git clone https://github.com/maciejtreder/asynchronous-javascript.git cd asynchronous-javascript git checkout step6 npm install cd promises
.then
and .catch
methods. These methods form most of the links in a chain of promises..then
method of the Promise object prototype appends handler functions to a Promise object. It contains both an onResolved
parameter and an onRejected
parameter so you can pass functions to the .then
method to handle cases where the promise is successful and those which result in an error (or another condition requiring rejection of the promise). The .then
method returns a new Promise object with the results of the relevant handler function. In simpler terms, the .then
method enables you to specify actions to take on the value of a promise depending on whether is is fulfilled or rejected..then
method in the previous post in this series, Asynchronous JavaScript: Introduction to JavaScript Promises. Look for the Getting started with JavaScript Promises section..then
method in the companion repository for this series. The code in the promises/practical.js file provides an example in which the functions passed in the onResolve
and onRejected
parameters are called randomly based on the results of the asynchronous function returning the promise..catch
method of the Promise prototype to handle rejected promises. You can see an example in the promises/catch.js file the companion repository. The code refactors practical.js to use .catch
instead of the onRejected
argument of the .then
method..finally
method of the Promise prototype. You’ll learn more about the .finally
method in this post..then
and .catch
. The .then
method specifies the action to perform when a promise is fulfilled. The .catch
method specifies what to do when promise is rejected. This is usually when an error occurs, but you can also reject a promise explicitly without generating an error (although it is good practice to do so). Because those two methods return Promise
objects, you can use .then
and .catch
on it again and again:function multiply(a) { console.log(a); if (a > 16) throw `a is higher than 16`; return Promise.resolve(a*a); } Promise.resolve(2) // Returns a fulfilled promise with value 2 .then(multiply) // console.log(2); Promise fulfilled, returns 4; .then(multiply) // console.log(4); Promise fulfilled, returns 16 .then(multiply) // console.log(16); Promise fulfilled, returns 256 .then(multiply) // console.log(256); Promise rejected, throws `a is higher than 16` .then(multiply) // not reached .catch(error => {console.log(`Caught: ${error}`); return 3;}) //Catches error ‘a is higher than 16’ and returns 3 .then(multiply) // console.log(3); Promise fulfilled, returns 9 .then(console.log) //console.log(9); Promise fulfilled, returns void/undefined
.then
and .catch
methods can change which methods are called if an error is thrown. This can cause multiple execution paths through your chain of promises, depending on where errors are thrown and caught, as shown in the example above.{ "product":"astro", "init":"2019092706", "dataseries":[ { "timepoint":3, "cloudcover":1, "seeing":7, "transparency":3, "lifted_index":2, "rh2m":10, "wind10m":{ "direction":"S", "speed":2 }, "temp2m":21, "prec_type":"none" }, { "timepoint":6, "cloudcover":1, "seeing":8, "transparency":3, "lifted_index":2, "rh2m":12, "wind10m":{ "direction":"S", "speed":2 }, "temp2m":16, "prec_type":"none" }, ... ] }
‟...”
) is used to represent a portion of the data redacted for brevity.dataseries[n].temp2m
key. Extracting just the temperature from this data series can easily produce the JavaScript “pyramid of doom” if you use callbacks. (An example of a code pyramid can be found in Asynchronous JavaScript: Understanding Callbacks in the section Nested callbacks: creating a chain of asynchronous functions.)fetch
functionality (exactly the same as in modern browsers) to the Node.js environment.npm i node-fetch
const fetch = require('node-fetch'); const lat = 37; const lon = 122; fetch(`http://www.7timer.info/bin/api.pl?lon=${lon}&lat=${lat}&product=astro&output=json`) .then(response => response.json()) .then(response => response.dataseries[0].temp2m) .then(temperature => console.log(`The temperature reported by the 7timer is: ${temperature} C`));
.then
method calls is a chain. The first link takes the Response
object from the www.7timer.info API call and executes the Body.json()
method on it. The Body.json
method reads the Response
stream and returns a promise with a JSON object containing the data from the Response
stream as its value. The next .then
method finds a specific data element in the JSON object and returns a promise with the retrieved data element as its value. A third .then
method is applied to this promise chain to take the temperature value and send it to standard output.node chaining.js
The temperature reported by the 7timer is: 21 C
git clone https://github.com/maciejtreder/asynchronous-javascript.git cd asynchronous-javascript git checkout step7 npm install cd promises
const group = [ fetch(`url1.com/path`), fetch(`url2.com/path`), fetch(`url3.com/path`) ] group.push(fetch(`url4.com/path`));
Promise.all
is a static function which combines an array of promises into one promise. The consolidated promise state is determined when all the promises in the array have been settled (resolved or rejected). If any promise in a collection is rejected the result of the Promise.all
method is a rejected promise with the rejection reason taken from the first failing promise. The result of a Promise.all
method evaluation can only be a fulfilled promise when all the promises in the array are settled as fulfilled. In other words, it is an “all” or nothing deal.const fetch = require('node-fetch'); const lat = 37; const lon = 122; const sevenTimerTemp = fetch(`http://www.7timer.info/bin/api.pl?lon=${lon}&lat=${lat}&product=astro&output=json`) .then(response => response.json()) .then(response => response.dataseries[0].temp2m); const fccWeatherTemp = fetch(`https://fcc-weather-api.glitch.me/api/current?lat=${lat}&lon=${lon}`) .then(response => response.json()) .then(response => response.main.temp); Promise.all([sevenTimerTemp, fccWeatherTemp]) .then(responses => { let sum = 0; let count = 0; responses.forEach(response => { sum += response; count++; }); return sum / count; }) .then(average => console.log(`Average of reported temperatures is: ${average} C`)) .catch(() => console.log(`One of the APIs returned an error`));
node all.js
Average of reported temperatures is: 21.615000000000002 C
One of the APIs returned an error
Promise.all
method; it immediately rejects if one of the provided promises rejects. It doesn’t even wait for all the promises to be settled..catch
method.Promise.all([ sevenTimerTemp.catch(() => -100), fccWeatherTemp.catch(() => -100) ]) .then(responses => { let sum = 0; let count = 0; responses.filter(response => response !== -100).forEach(response => { sum += response; count++; }); return sum / count; }) .then(average => console.log(`Average of reported temperatures is: ${average} C`));
Promise.all
. Each of the constants passed to the Promise.all
method in the array is modified with the .catch
method. If the promise state associated with the constant is rejected
because of an invalid HTTP response from the associated API, the .catch
method will set the value of the associated promise to -100. In the first .then
handler method attached to the Promise object returned by the Promise.all method
the code skips that unrealistic value when calculating the average temperature.Promise.allSettled
method, which is currently in the proposal state. Because this functionality is not supported by most browsers or by Node.js in versions below 12.9, it’s a good practice to use the promise.allSettled shim available in the npm repository.npm i promise.allsettled
const fetch = require('node-fetch'); const allSettled = require('promise.allsettled'); allSettled.shim(); const lat = 37; const lon = 122; const sevenTimerTemp = fetch(`http://www.7timer.info/bin/api.pl?lon=${lon}&lat=${lat}&product=astro&output=json`) .then(response => response.json()) .then(response => response.dataseries[0].temp2m); const fccWeatherTemp = fetch(`https://fcc-weather-api.glitch.me/api/current?lat=${lat}&lon=${lon}`) .then(response => response.json()) .then(response => response.main.temp); Promise.allSettled([sevenTimerTemp, fccWeatherTemp]) .then(responses => { let sum = 0; let count = 0; responses.filter(response => response.status === 'fulfilled').forEach(response => { sum += response.value; count++; }); return sum / count; }) .then(average => console.log(`Average of reported temperatures is: ${average} C`));
Promise
object prototype:const allSettled = require('promise.allsettled'); allSettled.shim();
allSettled
method on the Promise
class if it is not already enabled. If you are running Node.js version 12.9 or higher (and you probably are), then the .shim
method call results in a no-op and the native version of the static method is used.Average of reported temperatures is: 19.72
allSettled
method to be settled. The code in the .then
method call only includes the values from promises in the array that are fulfilled. If there is a REST call failure in any of the API calls, the status of the associated promise will be rejected
and its value skipped when calculating the average temperature.allSettled
method is always fulfilled: it never returns a status of rejected
. The value of the resolved promise is an array containing information about child promises statuses and values.Promise.race
method is intended for that purpose.const fetch = require('node-fetch'); const lat = 37; const lon = 122; const sevenTimerTemp = fetch(`http://www.7timer.info/bin/api.pl?lon=${lon}&lat=${lat}&product=astro&output=json`) .then(response => response.json()) .then(response => response.dataseries[0].temp2m); const fccWeatherTemp = fetch(`https://fcc-weather-api.glitch.me/api/current?lat=${lat}&lon=${lon}`) .then(response => response.json()) .then(response => response.main.temp); Promise.race([sevenTimerTemp, fccWeatherTemp]) .then(temperature => console.log(`Temperature reported by the fastest station is: ${temperature} C`));
Temperature reported by the fastest station is: 24.44 C
race
method rejects? The race
method will behave in a similar fashion to Promise.all
: it will reject immediately.Promise.all
method, chaining will do the trick.function getWatchdog(timeout) { return new Promise((resolve, reject) => setTimeout(() => reject('Timeout !'), timeout * 1000)); } Promise.race([ sevenTimerTemp.catch(() => new Promise((resolve) => {/*noop*/})), fccWeatherTemp.catch(() => new Promise((resolve) => {/*noop*/})), getWatchdog(5) ]) .then(temperature => console.log(`Temperature reported by the fastest station is: ${temperature} C`)) .catch(() => console.log('Unfortunately no API responded within the given time-out interval'));
.catch
method which returns a never-resolving promise that will be returned in the event of an exception. This technique guarantees that the Promise.race
promise won’t resolve until one of the promises provided as parameter does.Promise.race
call to complete? No. The getWatchdog
function, which returns a promise, resolves after a given amount of time.Temperature reported by the fastest station is: 28.23 C Temperature reported by the fastest station is: 28.23 C
setTimeout
in the getWatchDog
function executes after the Promise.race
method evaluation is complete.git clone https://github.com/maciejtreder/asynchronous-javascript.git cd asynchronous-javascript git checkout step8 npm install cd promises
.then
, .catch
, and .finally
to manipulate the results of the asynchronous operations in a chain of promises, and it explained how to manage the flow of control in your code with promise chaining. It also explained and demonstrated how to use the Promise object static methods to work with iterable collections of promises. JavaScript promises are a considerable improvement over basic callbacks and learning to use these advanced features of the Promise object can help you in many programming situations..then
, ,catch
and .finally
.