Item 27: Use async Functions Instead of Callbacks to Improve Type Flow
Prefer Promises to callbacks for better composability and type flow.
Prefer async
and await
to raw Promises when possible. They produce more concise, straightforward code and eliminate whole classes of errors.
If a function returns a Promise, declare it async
.
declare function fetchURL (
url : string , callback : ( response : string ) => void
) : void ;
fetchURL ( url1 , function ( response1 ) {
fetchURL ( url2 , function ( response2 ) {
fetchURL ( url3 , function ( response3 ) {
// ...
console . log ( 1 ) ;
} ) ;
console . log ( 2 ) ;
} ) ;
console . log ( 3 ) ;
} ) ;
console . log ( 4 ) ;
// Logs:
// 4
// 3
// 2
// 1
💻 playground
const page1Promise = fetch ( url1 ) ;
page1Promise . then ( response1 => {
return fetch ( url2 ) ;
} ) . then ( response2 => {
return fetch ( url3 ) ;
} ) . then ( response3 => {
// ...
} ) . catch ( error => {
// ...
} ) ;
💻 playground
async function fetchPages ( ) {
const response1 = await fetch ( url1 ) ;
const response2 = await fetch ( url2 ) ;
const response3 = await fetch ( url3 ) ;
// ...
}
💻 playground
async function fetchPages ( ) {
try {
const response1 = await fetch ( url1 ) ;
const response2 = await fetch ( url2 ) ;
const response3 = await fetch ( url3 ) ;
// ...
} catch ( e ) {
// ...
}
}
💻 playground
async function fetchPages ( ) {
const [ response1 , response2 , response3 ] = await Promise . all ( [
fetch ( url1 ) , fetch ( url2 ) , fetch ( url3 )
] ) ;
// ...
}
💻 playground
function fetchPagesWithCallbacks ( ) {
let numDone = 0 ;
const responses : string [ ] = [ ] ;
const done = ( ) => {
const [ response1 , response2 , response3 ] = responses ;
// ...
} ;
const urls = [ url1 , url2 , url3 ] ;
urls . forEach ( ( url , i ) => {
fetchURL ( url , r => {
responses [ i ] = url ;
numDone ++ ;
if ( numDone === urls . length ) done ( ) ;
} ) ;
} ) ;
}
💻 playground
function timeout ( timeoutMs : number ) : Promise < never > {
return new Promise ( ( resolve , reject ) => {
setTimeout ( ( ) => reject ( 'timeout' ) , timeoutMs ) ;
} ) ;
}
async function fetchWithTimeout ( url : string , timeoutMs : number ) {
return Promise . race ( [ fetch ( url ) , timeout ( timeoutMs ) ] ) ;
}
💻 playground
async function getNumber ( ) { return 42 ; }
// ^? function getNumber(): Promise<number>
💻 playground
const getNumber = async ( ) => 42 ;
// ^? const getNumber: () => Promise<number>
💻 playground
const getNumber = ( ) => Promise . resolve ( 42 ) ;
// ^? const getNumber: () => Promise<number>
💻 playground
// Don't do this!
const _cache : { [ url : string ] : string } = { } ;
function fetchWithCache ( url : string , callback : ( text : string ) => void ) {
if ( url in _cache ) {
callback ( _cache [ url ] ) ;
} else {
fetchURL ( url , text => {
_cache [ url ] = text ;
callback ( text ) ;
} ) ;
}
}
💻 playground
let requestStatus : 'loading' | 'success' | 'error' ;
function getUser ( userId : string ) {
fetchWithCache ( `/user/${ userId } ` , profile => {
requestStatus = 'success' ;
} ) ;
requestStatus = 'loading' ;
}
💻 playground
const _cache : { [ url : string ] : string } = { } ;
async function fetchWithCache ( url : string ) {
if ( url in _cache ) {
return _cache [ url ] ;
}
const response = await fetch ( url ) ;
const text = await response . text ( ) ;
_cache [ url ] = text ;
return text ;
}
let requestStatus : 'loading' | 'success' | 'error' ;
async function getUser ( userId : string ) {
requestStatus = 'loading' ;
const profile = await fetchWithCache ( `/user/${ userId } ` ) ;
requestStatus = 'success' ;
}
💻 playground
async function getJSON ( url : string ) {
const response = await fetch ( url ) ;
const jsonPromise = response . json ( ) ;
return jsonPromise ;
// ^? const jsonPromise: Promise<any>
}
getJSON
// ^? function getJSON(url: string): Promise<any>
💻 playground