Introduction
For asynchronous programming, there are few options there. Few years back, callback was a common way for developers to handle asynchronous call. However, it led us to callback hell once your code getting more complicated and with nested callbacks. Then, Promise started getting popular. Recently, you may have heard of Reactive Programming that may give us another tool for it. In this article, I will go thru some best practices and live examples that helps us to use Promise properly in asynchronous programming. Next article, I will go over RxJS, a Javascript implementation of the Reactive Extension(Rx), and when RxJS is preferable option to Promise.
Promise
Promises are good for solving asynchronous operations such as querying a service with an XMLHttpRequest, where the expected behavior is one value and then completion. Before writing code to consumer the Promise, let’s first be the producer.
Produce a Promise
There are only 3 states for Promise: Pending, Fulfilled and Rejected. The parameter of new Promise() is called an executor. If the value is obtained, it will call the resolve() method with the result as parameter otherwise it will call reject() method with the error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// general form const p = new Promise( function (resolve, reject) { ··· if (···) { resolve(value); // success } else { reject(reason); // failure } }); // create immediate Promise Promise.resolve('abc') .then(x => console.log(x)); // example: promisify the XMLHttpRequest function httpGet(url) { return new Promise( function (resolve, reject) { const request = new XMLHttpRequest(); request.onload = function () { if (this.status === 200) { // Success resolve(this.response); } else { // Something went wrong (404 etc.) reject(new Error(this.statusText)); } }; request.onerror = function () { reject(new Error( 'XMLHttpRequest Error: '+this.statusText)); }; request.open('GET', url); request.send(); }); } |
Consume the Promise
To consume the promise, you can see the following pattern. Sometimes, you may see developer write Promise code with the old callback style. Try not doing it as it will make your code hard to maintain and potentially lose the reason of the error.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// general form promise .then(value => { /* fulfillment */ }) .catch(error => { /* rejection */ }); // consume the httpGet promise created above httpGet('http://example.com/file.txt') .then(function (result) { console.log('Contents: ' + value); }) .catch(function (reason) { console.error('Something went wrong', reason); }); |
Chained Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// Don't do this as old style callback hell - hard to read firstAsync.then(result1 => { secondAsync() .then(result2 => { ··· }); }); // flatten nested Promise like this if secondAsync return a Promise as well firstAsync() .then(function (value1) { return secondAsync(); //or secondAsync(value1) if it needs firstAsync result. }) .then(function (value2) { ··· }) // or use lambda here firstAsync() .then(result1 => asyncFunc2()) .then(result2 => {···}); |
Run a list of Promise in parallel or in series
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// to avoid nested promise you can use promise.all(list) that will have all async calls run in parallel Promise.all([firstAsync, secondAsync]) .then(function(results) { // do something with result1 and result2 // available as results[0] and results[1] respectively }) .catch(function(err) { /* ... */ }); // if you need the result from first call for the 2nd call, you can do the following firstAsync() .then(function(result1) { return Promise.all([result1, secondAsync(result1)]); }) .then(function(results) { // do something with results array: results[0], results[1] }) .catch(function(err){ /* ... */ }); |
Now you know how to prepare results from dependent async calls and make all results available on 2nd then(). How about writing generic code so you can have arbitrary numbers of async calls that chained up together.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Promise returning functions to execute function doFirstThing(){ return Promise.resolve(1); } function doSecondThing(res){ return Promise.resolve(res + 1); } function doThirdThing(res){ return Promise.resolve(res + 2); } function lastThing(res){ console.log("result:", res); } var fnlist = [ doFirstThing, doSecondThing, doThirdThing, lastThing]; // Execute a list of Promise return functions in series function pseries(list) { var p = Promise.resolve(); return list.reduce(function(pacc, fn) { return pacc = pacc.then(fn); }, p); } pseries(fnlist); // result: 4 |
What is good about Promise
- Promises give us the ability to write asynchronous code in a synchronous fashion, with flat indentation and a single exception channel.
- Promises help us unify asynchronous APIs and allow us to wrap non-spec compliant Promise APIs or callback APIs with real Promises.
- Promises give us guarantees of no race conditions and immutability of the future value represented by the Promise (unlike callbacks and events).
- As soon as a Promise is created it begins executing
Drawback of Promise
But, Promises aren’t without some drawbacks as well:
- You can’t cancel a Promise, once created it will begin execution. If you don’t handle rejections or exceptions, they get swallowed.
- You can’t determine the state of a Promise, ie whether it’s pending, fulfilled or rejected. Or even determine where it is in it’s processing while in pending state.
- If you want to use Promises for recurring values or events, there is a better mechanism/pattern for this scenario called streams.
References
- https://medium.com/javascript-scene/the-hidden-power-of-es6-generators-observable-async-flow-control-cfa4c7f31435
- https://github.com/ericelliott/ogen
- https://github.com/jhusain/learnrxjava/
- https://miguelmota.com/blog/getting-started-with-rxjs/
- Promise patterns and anti-patterns – Great article shows you how to write promise properly
- RxJS Observables Crash Course – For the beginner, this is a great video to show you how to code with RxJs.
- Learning Observable By Building Observable – This article shows you how to create your own Observable from scratch.
Connect with us