Promises & Asynchronicity
Intro
Promise()
is built into JavaScript since ES6.
Promise()
constructor function is a built-in JavaScript object an instance of which represents a promise of returning a value. Such a promise is pending until it is settled through being resolved (aka fulfilled) or rejected.
Creating a Promise
A promise in JavaScript is created through using of Promise()
constructor function. The constructor accepts two callback functions. The first one is to be executed if the code instructions underpinning the promise succeed and the second one if they fail. The first callback function's argument is the value which is returned by the promise if it resolves (aka fulfils). The second callback function's argument is the error that shall be thrown by the promise if the promise is rejected.
const twoAndTwoPromise = new Promise(function(resolve, reject) {
const sum = 2 + 2
if (sum === 4) {
resolve('2 and 2 does equal 4')
} else {
reject(Error('2 and 2 does not equal 4!'))
}
})
The above promise is always fulfilled as 2 and 2 always equals 4 (in JavaScript). However, when a more elaborate instructions are provided - such as connecting with external resources - they might as well fail for many reasons such as unexpected response status or a network error. Then the reject callback can be used to reject the whole promise.
Asynchronicity of a Promise
When a XMLHttpRequest()
request is being made within a promise the promise does not block other instructions from outside its body to be executed when the request is pending. Instead, once the request is completed (successfully or not) only then the resolve or reject callback functions are being executed.
const getUrl = function(url) {
return new Promise(function(resolve, reject) {
const request = new XMLHttpRequest()
request.open('GET', url)
request.onload = function() {
if (request.status === 200) {
resolve(request.response)
} else {
reject(Error(`Unable to get ${url}!`))
}
}
request.onerror = function() {
reject(Error('Network error!'))
}
request.send()
})
}
console.log(getUrl('https://soundof.it/edit-page/javascript-tutorial'))
console.log('After promise!')
// Promise {<pending>}
// After promise!
The promise is in a state of pending
during the time after the its execution but before its fulfilment or rejection.
Using Values Returned by Promises
Then
As already noted, once a promise is executed the directly subsequent code from outside its body is being executed before the promise is fulfilled or rejected.
const promiseValue = getUrl('https://www.codelumi.com')
console.log(promiseValue)
console.log('Here!')
// Promise {<pending>}
// Here!
To use a value returned by a promise upon its fulfilment the Promise.prototype.then()
needs to be appended to the promise.
getUrl('https://www.codelumi.com') // getUrl() returns a promise.
.then(value => console.log(value))
console.log('Here!')
// Here!
// <!doctype html> ...
Promise.prototype.then()
accepts two callback functions as arguments. The first callback is called when the promise is fulfilled and the second when the promise is rejected.
getUrl('https://www').then(value => console.log(value), error => console.log(error))
console.log('Here!')
// Here!
// Error: Network error! ...
// => GET https://www/ net::ERR_NAME_NOT_RESOLVED
Catch
Instead of using the second callback of Promise.prototype.then()
to reach the promise error the error can also be caught using Promise.prototype.catch()
which accepts only one callback function dedicated to the error handling.
getUrl('https://www').catch(error => console.log(error))
console.log('Here!')
// Here!
// Error: Network error! ...
// => GET https://www/ net::ERR_NAME_NOT_RESOLVED
Finally
There is also Promise.prototype.finally()
which is called irrespective of whether the promise was resolved or rejected.
getUrl('https://www')
.catch(error => console.log(error))
.finally(() => console.log('It\'s finally over!'))
console.log('Here!')
// Here!
// Error: Network error! ...
// It's finally over!
// => GET https://www/ net::ERR_NAME_NOT_RESOLVED
Promise Composition aka Promise Chaining
If one of the callback handler functions provided to Promise.prototype.then()
or Promise.prototype.catch()
:
-
returns a value - then
then()
returns a promise that is to be fulfilled with that value, -
returns a pending promise - then
then()
returns the promise, -
throws an error - then
then()
returns a rejected promise with the error of the rejected promise.
As then()
and catch()
return promises themselves they can be chained with subsequent then()
s and/or catch()
es.
getUrl('https://soundof.it/javascript-tutorial')
.then(value => {
console.log(value)
return getUrl('https://soundof.it/ruby-tutorial')
})
.then(value => {
console.log(value)
})
// => Promise {<pending>}
// <!doctype html> ... <title>JavaScript Tutorial</title> ...
// <!doctype html> ... <title>Ruby Tutorial</title> ...
`Promise()` Static Methods
Promise()
constructor function object comes with many built-in static methods, such as:
-
resolve(value)
- returns a promise object that is to be resolved with the provided value, -
reject(error)
- returns a promise object that is to be rejected with the provided error, -
all(promises)
- tries to resolve all promises, -
any(promises)
- tries to resolve at least one promise from the promises, -
allSettled(promises)
- settles all promises, irrespective of whether they resolve or reject, -
race(promises)
- waits for the settlement of the first promise.
Async / Await
async
and await
functionality was introduced by ECMAScript 2017.
When a promise is appended with then()
the code within one of the then()
callback functions will always be executed after the code following directly the promise chain.
getUrl('https://soundof.it/javascript-tutorial')
.then(value => {
console.log(value)
})
console.log('Outside of the promise!')
// Outside of the promise chain!
// <!doctype html> ...
async
and await
keywords allow for asynchronous promise-based behaviour to be written in a way mimicking a synchronous code.
async function getUrlAndLogIt(url) {
console.log(await getUrl(url))
console.log('Outside of the promise!')
}
console.log('Outside of the async function getUrlAndLogIt!')
getUrlAndLogIt('https://soundof.it/javascript-tutorial')
// Outside of the async function getUrlAndLogIt!
// <!doctype html> ...
// Outside of the promise!
The code following the await
keyword within an async
function is not executed until the promise prepended with the await
keyword is settled.
An async function implicitly returns a promise that is resolved with the value explicitly returned by the async function unless rejected due to an uncaught exception.