Promises In JavaScript: A Complete Guide
An object standing for the ultimate success or failure of an asynchronous activity is a promise. This tutorial will discuss consumption of returned promises first, then go on to discuss how to generate them as most individuals are consumers of already-made promises.
A promise is essentially a returned object onto which you attach callbacks rather than providing callbacks into a method. Imagine a method, createAudioFileAsync(), which asynchronously produces a sound file given a configuration record and two callback functions: one invoked if the audio file is successfully generated, and the other called should an error arise.
The following code employs createAudioFileAsync():
function successCallback(result) {
console.log(`Audio file ready at URL: ${result}`);
}
function failureCallback(error) {
console.error(`Error generating audio file: ${error}`);
}
createAudioFileAsync(audioSettings, successCallback, failureCallback);
You would connect your callbacks to createAudioFileAsync() instead of rewriting it to fulfill a promise:
createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
This conference offers several benefits. We will look at every one.
Chaining
Executing two or more asynchronous actions back to back is a typical necessity whereby each succeeding operation starts after the preceding operation succeeds, with the result from the previous step. Doing numerous asynchronous actions in a succession in the past would cause the traditional callback hell:
doSomething(function (result) {
doSomethingElse(result, function (newResult) {
doThirdThing(newResult, function (finalResult) {
console.log(`Got the final result: ${finalResult}`);
}, failureCallback);
}, failureCallback);
}, failureCallback);
Promises help us to fulfill this by means of a promise chain. Promises have fantastic API design since callbacks attached to the delivered promise object rather than being handed into a method.
The secret is the then() method generates a fresh promise distinct from the original:
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
Error handling
You might remember seeing failure callback three times in the pyramid of doom previously, contrasted to only once at the promise chain’s end:
doSomething()
.then((result) => doSomethingElse(result))
.then((newResult) => doThirdThing(newResult))
.then((finalResult) => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
Should there be an exception, the browser will search onRejected or for catch() handlers along the chain. This is rather closely modeled after synchronous code operation:
try {
const result = syncDoSomething();
const newResult = syncDoSomethingElse(result);
const finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch (error) {
failureCallback(error);
}
This symmetry with asynchronous programming ends with the async/await syntax:
async function foo() {
try {
const result = await doSomething();
const newResult = await doSomethingElse(result);
const finalResult = await doThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch (error) {
failureCallback(error);
}
}
Task queues vs. microtasks
SetTimeout() callbacks are treated as task queues; promise callbacks are handled as microtasks.
const promise = new Promise((resolve, reject) => {
console.log("Promise callback");
resolve();
}).then((result) => {
console.log("Promise callback (.then)");
});
setTimeout(() => {
console.log("event-loop cycle: Promise (fulfilled)", promise);
}, 0);
console.log("Promise (pending)", promise);
The above codes will produce:
Promise callback
Promise (pending) Promise {<pending>}
Promise callback (.then)
event-loop cycle: Promise (fulfilled) Promise {<fulfilled>}
When responsibilities and promises cross paths
Using a microtask to verify status or balance out your promises might help you if you find yourself in circumstances whereby your promises and tasks — such as those arising from events or callbacks — are firing in erratic patterns.
See the microtask documentation to learn more about how to use queueMicrotask() to enqueue a function as a microtask should you believe microtasks might help address this problem.