Syntax Promise version
const promise = new Promise((resolve, reject) => {
/* executor code */
resolve('done')
reject(new Error('failed'))
})
promise
.then(
result => { console.log(result) },
error => { console.log(error) }
)
.catch(error => { console.log(error) })
.finally(() => { /* code */ })
Async await version
async func() {
try {
await promise
/* code */
}
finally {
/* code */
}
catch(error) {
console.log(error)
}
}
Promise object runs an asynchronous operation + stores its resulting value + completion (or failure) state When new promise is created, the executor runs Its arguments resolve & reject are default callbacks When function is executed resolve() or reject() callbacks should be called resolve(result) if the job is finished successfully reject(error) if an error has occurred resolve() or reject() should be call only ones. All further calls of resolve() or reject() are ignored resolve() & reject() expect only one argument (or none) In reject() recommended to use an Error object Resolved or rejected promise is called settled , as opposed to an initially pending Returned promise object has internal properties: state [[PromiseState]] = "pending" initial state [[PromiseState]] = "fulfilled" when resolve() is called [[PromiseState]] = "rejected" when reject() is called result [[PromiseResult]] = "undefined" initial result [[PromiseResult]] = "value" when resolve() called [[PromiseResult]] = "error" when reject() is called We can’t directly access state & result properties But can access them through methods .then() , .catch() , .finally()
// the function is executed automatically when the promise is constructed
// after 5s job is done with the result "done" or rejected
// resolve
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done"), 5000)
})
// reject
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 5000)
})
// both in one statement
let promise = new Promise(function(resolve, reject) {
resolve("done")
reject(new Error("…")) // ignored
setTimeout(() => resolve("…")) // ignored
});
then, catch, finally Promise object serves as a link between the executor and the consuming functions promise.then()
promise.then(
function(result) {},
function(error) {}
)
// 1st fn - handles a successful result, when the promise is resolved, and receives the result
// 2nd fn - handles an error, when the promise is rejected, and receives the error
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000)
// or
setTimeout(() => reject(new Error("Whoops!")), 1000)
})
promise.then(
result => alert(result), // shows "done!" after 1 second
error => alert(error) // doesn't run
)
One argument is acceptable
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000)
})
promise.then(alert) // shows "done!" after 1 second
promise.catch()
// If we’re interested only in errors use null as a 1st arg or use .catch(errorHandlingFunction)
promise.then(null, errorHandlingFunction)
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
})
// .catch(f) is the same as promise.then(null, f)
promise.catch(alert) // shows "Error: Whoops!" after 1 second
promise.finally() .finally(f) is similar to .then(f, f) Always runs when the promise is settled (resolved or rejected) Finally is a good handler for performing cleanup, e.g. stopping our loading indicators Finally handler has no arguments Finally handler passes through results and errors to the next handler
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.then(result => alert(result)) // <-- .then handles the result
.catch(err => alert(err)) // <-- .catch handles the error object, if there is any
.finally(() => alert("Promise ready"))
Example
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(script)
script.onerror = () => reject(new Error(`Script load error for ${src}`))
document.head.append(script)
})
}
// usage
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js")
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
)
promise.then(script => alert('Another handler...'))
Promise vs regular code Regular code
console.log(1)
console.log(2)
setTimeout(() => console.log(3), 1000)
console.log(4)
// 1, 2, 4, 3
Promise code
console.log(1)
console.log(2)
const log3 = new Promise(resolve => {
setTimeout(() => {
console.log(3)
resolve('done')
}, 1000)
})
log3.then(result => console.log(4))
// 1, 2, 3, 4
Promises chaining Result is passed through the chain of then/catch/finally handlers Handler returns “thenable” object, that has a method .then() Returning promises allows us to build chains of asynchronous actions
new Promise( resolve => {
setTimeout(() => resolve(1), 1000)
}).then( result => {
alert(result) // 1
return result * 2
}).then( result => {
alert(result) // 2
return result * 2
}).then( result => {
alert(result) // 4
return result * 2
}) // 1 // 2 // 4
Same as above, but with 1s delay between alerts
new Promise( resolve => {
setTimeout(() => resolve(1), 1000)
}).then( result => {
alert(result) // 1
return new Promise( resolve => {
setTimeout(() => resolve(result * 2), 1000) // 2
})
}).then( result => {
alert(result) // 2
return new Promise( resolve => {
setTimeout(() => resolve(result * 2), 1000) // 4
})
}).then( result => {
alert(result) // 4
})
Make function thenable / chainable To make a function thenable, just return a promise
// not thenable
function func() {
setTimeout(() => '2 sec passed', 2000)
}
func().then(res => console.log(res)) // TypeError: Cannot read properties of undefined (reading 'then')
// thenable
function funcThenable() {
return new Promise(resolve => {
setTimeout(() => resolve('2 sec passed'), 2000)
})
}
funcThenable().then(res => console.log(res)) // 2 sec passed
Error handling We may have many .then() handlers, and use a single .catch() at the end If any of the promises above rejects, then it would catch it
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.catch(error => alert(error.message))
new Promise((resolve, reject) => {
reject(new Error("Whoops!"))
}).catch(err => alert(err)); // Error: Whoops!
// same as
new Promise((resolve, reject) => {
throw new Error("Whoops!")
}).catch(err => alert(err)); // Error: Whoops!
// error in then()
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("Whoops!"); // rejects the promise
}).catch(err => alert(err)); // Error: Whoops!
// catch also handles programming errors
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // no such function
}).catch(err => alert(err)); // ReferenceError: blabla is not defined
// all synchronous errors while the executor is running are handled by try...catch
// catch() can't handle this error
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!");
}, 1000);
}).catch(err => alert(err));
// Unhandled rejections
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
// The JS engine tracks such rejections and generates a global error
window.addEventListener('unhandledrejection', function(event) {
console.log(event.promise); // [object Promise] - the promise that generated the error
console.log(event.reason); // Error: Whoops! - the unhandled error object
})
Promise.all() Takes an array of promises Waits for all promises to resolve and returns an array of their results If any of the given promises rejects, it throws an error & other results are ignored
let p1 = new Promise(resolve => setTimeout(() => resolve(1), 3000))
let p2 = new Promise(resolve => setTimeout(() => resolve(2), 2000))
let p3 = new Promise(resolve => setTimeout(() => resolve(3), 1000))
Promise.all([p1, p2, p3]).then(values => console.log(values)) // [1, 2, 3]
// same, but with destructuring
Promise.all([p1, p2, p3]).then(([val1, val2, val3]) => console.log('results: ', val1, val2, val3))
Rejection
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert) // Error: Whoops!
Mix promises with regular values
let promiseObj = new Promise(resolve => setTimeout(() => resolve(1), 1000))
Promise.all([promiseObj, 2, 3]).
then((res) => console.log(res)) // 1, 2, 3
Promise.all & fetch Fetch returns a promise We can put them in array with .map() Simple example without reading content
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
]
let requests = urls.map(url => fetch(url))
Promise.all(requests)
.then(responses => responses.forEach(
response => console.log(`${response.url}: ${response.status}`)
))
With reading content
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => Promise.all(responses.map(r => r.json())))
.then(users => users.forEach(user => console.log(user.name)))
Promise.allSettled() Waits for all promises to settle, regardless of the result
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
]
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => {
results.forEach((result, num) => {
if (result.status == "fulfilled") console.log(`${urls[num]}: ${result.value.status}`)
if (result.status == "rejected") console.log(`${urls[num]}: ${result.reason}`)
})
})
// https://api.github.com/users/iliakan: 200
// https://api.github.com/users/remy: 200
// https://no-such-url: TypeError: Failed to fetch
Promise.race() Waits only for the first settled promise and gets its result
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert) // 1
// 1st promise was fastest, it became the result
// 1st settled promise “wins the race”, all further results/errors are ignored
Promise.any() Waits only for the first fulfilled promise and gets its result If all promises are rejected , then the returned promise is rejected with AggregateError Special error object that stores all promise errors in its errors property
// first promise here was fastest, but it was rejected, so the second promise became the result
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
// example when all promises fail
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000))
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error
});
Promise.resolve() & Promise.reject() Creates a resolved or rejected promise with the result value or error The method is used for compatibility, when a function is expected to return a promise Rarely needed in modern code, because of async/await syntax Same as let promise = new Promise(resolve => resolve(value)) Same as let promise = new Promise((null, reject) => reject(error))
const status = res => {
if (res.status >= 200 && res.status < 300) return Promise.resolve(res)
return Promise.reject(new Error(res.statusText))
}
const json = res => res.json()
fetch('/todos.json')
.then(status) // note that the 'status' function is actually **called** here, and that it **returns a promise***
.then(json) // likewise, the only difference here is that the 'json' function here returns a promise that resolves with 'data'
.then(data => { // ... which is why 'data' shows up here as the first parameter to the anonymous function
console.log('Request succeeded with JSON response', data)
})
.catch(err => {
console.log('Request failed', err)
})
Task queue When a promise is ready, its .then/catch/finally handlers are put into the microtasks queue When the JS engine becomes free from the macrotask , it executes code from microtasks queue To guarantee that code is executed code in .then/catch/finally , add it into a chained .then call
function funcA() {
console.log(1)
setTimeout(() => { console.log(2) })
console.log(3)
Promise.resolve()
.then(res => console.log(4))
.then(res => console.log(5))
console.log(6)
}
function funcB() {
console.log(7)
}
funcA()
funcB()
// 1 3 6 4 5 2
Common errors TypeError: undefined is not a promise - make sure you use new Promise() instead of just Promise() UnhandledPromiseRejectionWarning - promise you called rejected, but there was no catch used to handle the error async/await It is a special syntax to work with promises without chaining Just another way of getting the promise result than .then() async makes function return a promise Values are wrapped in a resolved promise automatically await makes JS wait until a promise settles and returns its result await works only inside async Can’t use await in regular functions Top-level await works outside async functions in modules, in Chrome DevTools & Node.js & Safari Web Inspector
async function f() { return 1 }
f().then(alert) // 1
await for promise
// example 1
let promise = new Promise(resolve => setTimeout(() => resolve("done!"), 1000))
promise // Promise {<fulfilled>: 'done!'} // not the result itself
let result = await promise // wait until the promise resolves
alert(result) // "done!"
// example 2
async function hi() { return 'hi' }
hi() // Promise {<fulfilled>: 'hi'}
// but
const res = await hi()
alert(res) // 'hi'
// example 3
let response = await fetch('https://api.github.com/users/iliakan')
let user = await response.json()
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
console.log(user)
// example 4
await new Promise(resolve => setTimeout(resolve, 1000))
return 10
await accepts “thenables” await allows to use thenable objects (with a callable then method) The idea is that a third-party object may not be a promise, but promise-compatible if an object supports .then, that’s enough to use it with await
class Thenable {
constructor(num) { this.num = num }
then(resolve, reject) {
setTimeout(() => resolve(this.num * 2), 1000) // resolve with this.num*2 after 1000ms
}
}
let result = await new Thenable(1) // waits for 1 second, then result becomes 2
alert(result) // 2
async class methods
// To declare an async class method, just prepend it with async
class Waiter {
async wait() {
return await Promise.resolve(1)
}
}
new Waiter()
.wait()
.then(alert); // 1 (this is the same as (result => alert(result)))
Error handling
try {
let response = await fetch('http://no-such-url')
let user = await response.json()
} catch(err) {
alert(err) // TypeError: failed to fetch
}
// or
async function f() {
let response = await fetch('http://no-such-url');
}
f().catch(alert); // TypeError: failed to fetch
// or
// if we do not have catch we may handle unhandled errors with
window.addEventListener('unhandledrejection', function(event) {
alert(event.promise)
alert(event.reason)
})
async / await + Promise.all()
let p1 = new Promise(res => setTimeout(() => res(1), 3000))
let p2 = new Promise(res => setTimeout(() => res(2), 2000))
let p3 = new Promise(res => setTimeout(() => res(3), 1000))
Promise.all([p1, p2, p3]).then(values => console.log(values)) // [1, 2, 3]
// same as
let res = await Promise.all([p1, p2, p3])
console.log(res)
Parallel vs sequential vs batches we may run async functions in parallel with Promise.all() or one one by one with for... of loop or split in sequential batches or parallel promises with p-map package
import axios from 'axios'
import pMap from 'p-map'
async function getPostTitle({ postNumber }) {
const { data } = await axios(`https://jsonplaceholder.typicode.com/posts/${postNumber}`)
return data.title
}
function Component() {
const [isLoading, setIsLoading] = useState(false)
const [postTitles, setPostTitles] = useState([])
return (
<>
<div>
<button
onClick={async () => {
setIsLoading(true)
setPostTitles([])
const postTitles = await Promise.all([...Array(10).keys()].map((key) => getPostTitle({ postNumber: key + 1 })))
setPostTitles(postTitles)
setIsLoading(false)
}}
>
Get titles in parallel
</button>
</div>
<div>
<button
onClick={async () => {
setIsLoading(true)
setPostTitles([])
for (const key of [...Array(10).keys()]) {
const postTitle = await getPostTitle({ postNumber: key + 1 })
setPostTitles(prev => [...prev, postTitle])
}
setIsLoading(false)
}}
>
Get titles in series
</button>
</div>
<div>
<button
onClick={async () => {
setIsLoading(true)
setPostTitles([])
const postTitles = await pMap(
[...Array(10).keys()],
(key) => getPostTitle({ postNumber: key + 1 }),
{ concurrency: 3 }
)
setPostTitles(postTitles)
setIsLoading(false)
}}
>
Get titles in sequential batches of parallel promises
</button>
</div>
<div>isLoading: {isLoading.toString()} </div>
<div>titles:</div>
<ol>
{postTitles.map(title => <li key={title}>{title}</li>)}
</ol>
</>
)
}
parallel series sequential batches of parallel promises Promise.withResolvers new way to write promises without callbacks https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers new Promise
const sayHiAsync = () => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi!');
}, 1000);
})
return promise
}
const msg = await sayHiAsync()
console.log(msg)
Promise.withResolvers
const sayHiAsync = () => {
const { promise, resolve, reject } = Promise.withResolvers()
setTimeout(() => {
resolve('hi!')
}, 1000)
return promise
}
const msg = await sayHiAsync()
console.log(msg)