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.