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)