http://javascript.info/promise-basics
https://scotch.io/tutorials/javascript-promises-for-dummies
https://www.toptal.com/javascript/asynchronous-javascript-async-await-tutorial
https://stackoverflow.com/questions/39988890/do-javascript-promises-block-the-stack
Why do we need promises?
Say you have a url, when given two numbers, will return you the sum.
You give the url two numbers in its parameter, and issue a remote call to get the result.
Naturally, you need to wait for the result.
Sometimes, its slow in response, etc. You don’t want your entire process to be blocked while waiting for the result.
Calling APIs, downloading files, reading files are among some of the usual async operations that you’ll perform.
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
So this means once your Promise starts running (whether executing a timer or downloading from the internet) execution will continue on down the code. Once your Promise finishes (resolve or reject), then execution returns to the callback that you give to your Promise.
In this tutorial, we use setTimeout to simulate this server request response time.
1 2 3 4 5 6 7 8 9 |
function addAsync(num1, num2, callback) { setTimeout(function() { callback(num1 + num2); // result the result via callback }, 2000); // takes 2 seconds to simulate web } addAsync(1, 3, function(result) { console.log(result); // 4 }); |
The problem happens when we want to do callbacks in succession
say we want to add 1, 2, then get the result, and add 3. The get that
result and add 4.
If the calculation functionality is on a server, and we do this through this via callbacks, it would look like this:
1 2 3 4 5 6 7 8 9 |
addAsync(2, 4, function(result1) { console.log("result1: " + result1); addAsync(result1, 3, function(result2) { console.log("result2: " + result2); addAsync(result2, 8, function(result3) { console.log("result3: " + result3); }); }); }); |
result
info: querying url http://www.add.com
result1: 6
info: querying url http://www.add.com
result2: 9
info: querying url http://www.add.com
result3: 17
The syntax is less user friendly. In a nicer term, It looks like a pyramid, but people usually refer this as “callback hell”, because the callback nested into another callback. Imagine you have 10 callbacks, your code will nested 10 times!
Solution – Promise
Promises were the next logical step in escaping callback hell. This method did not remove the use of callbacks, but it made the chaining of functions straightforward and simplified the code, making it much easier to read.
Using promises in our previous example would make it simple.
First, we declare a function addAsync with two numbers to add.
We create a Promise object. Within our new created Promise object, we have a callback function parameter. We provide code that we want to execute within this callback. The callback function itself, has two parameters: resolve, and reject. Once the code has been executed and result is received, it will call resolve/reject in our code. This is so that a ‘then’ can be chained later on.
1 2 3 4 5 6 7 8 9 10 11 |
function addAsync(num1, num2) { let p = new Promise(function (resolve, reject) { ... resolve(num1+num2); }); return p; } let tmp = addAsync(1,2); |
How is it a then() can be chained later on? The Promise object is returned from our addAsync function, for others to use. In other words, when they execute this addAsync, they will be returned the Promise object.
The reason why we want to return the promise object is so that others can call then on it. Calling then on a Promise object means that you’ll get notified when a resolve or reject is called within the Promise. The first parameter callback of our then() will be triggered if its a resolve. 2nd parameter callback of our then() will be triggered if it’s a reject.
1 2 3 4 5 6 7 |
let tmp = addAsync(1,2); tmp.then(function(val) { // resolve from Promise obj in addAsync called }, function() { // rejected from Promise obj in addAsync called }); |
The then function simply waits for the resolve or reject to happen.
If its a resolve, the first callback parameter of the then will be executed. If reject, the 2nd callback parameter will be executed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function addAsync(num1, num2) { let p = new Promise((resolve, reject) => { setTimeout(function() { resolve(num1 + num2); }, 2000); }); return p; } addAsync(10, 10).then(result=> { // AFTER 2 seconds, num1+num2 is passed to parameter result console.log(result); }); |
Within setup of a Promise instance, we do a setTimeout to simulate a remote request that takes 2 seconds just like our previous example. Notice
that we don’t deal with custom callbacks. We now use resolve and then for the results.
Promise object’s resolve will trigger then()
1 2 3 |
addAsync(1, 2).then(success => { console.log("success! ...result is: " + success); }); |
Further Chaining…
ref – https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Once a Promise is fulfilled or rejected, the respective handler function (onFulfilled or onRejected parameters of then() ) will be called asynchronously (scheduled in the current thread loop). The behavior of the handler function follows a specific set of rules.
If a handler function:
returns a value, the promise returned by then gets resolved with the returned value as its value
1 2 3 4 |
let aPromise = addAsync(1,2); aPromise.then(function(value) { // resolve handler return value; }); |
// much later…
output of aPromise is:
Promise {
__proto__: Promise
[[PromiseStatus]]: “resolved”
[[PromiseValue]]: 3
doesn’t return anything
the promise returned by then gets resolved with an undefined value
1 2 3 |
let aPromise = addAsync(1,2); aPromise.then(function(value) { // resolve handler }); |
output of aPromise is:
Promise {
__proto__: Promise
[[PromiseStatus]]: “resolved”
[[PromiseValue]]: undefined
throws an error
So if we only call the reject, it’ll only hit the reject handler like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function executePromise(i) { return new Promise(function(resolve, reject) { if (i > 8) { resolve(i-1); } else { reject("Booo...less than 8."); } }); } executePromise(7).then(function(result) { // resolve console.log('RESOLVED: ' + result); }, function(err) { // reject console.log('REJECTED : ' + err); }).catch(function(err){ console.log('CAUGHT: ' + err); }); |
output: REJECTED : Booo…less than 8.
Note that if you DO NOT HAVE a reject handler, the reject will automatically be taken care of in the catch handler.
Normally, the catch handler catches any thing throw in the Promise object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function executePromise(i) { return new Promise(function(resolve, reject) { if (i > 8) { resolve(i-1); } else if ( i < 8 && i > 0 ) { throw 'Ooopssiiie'; } else { reject("Booo...less than 8."); } }); } executePromise(7).then(function(result) { console.log('RESOLVED: ' + result); }, function(err) { // reject console.log('REJECTED : ' + err); }).catch(function(err){ console.log('CAUGHT: ' + err); // comes here }); |
output:
REJECTED : Ooopssiiie
There is a situation where where you have an async operation inside of your Promise object. As a result, you throw 1 second later. It won’t catch because the Promise object has already been executed.
The only thing that works is reject, which naturally gets caught, given there is no reject handler.
1 2 3 4 5 6 7 8 9 10 11 |
new Promise(function(resolve, reject) { setTimeout(function() { //throw 'or nah'; // won't work // return Promise.reject('or nah'); also won't work reject('hehehehe'); }, 1000); }).catch(function(e) { console.log(e); // reject caught here }); |
catch is not like then(), where then() waits for the resolve/reject. Catch DOES NOT WAIT for the throw
The idea here is that you use resolve/reject in your Promise to take care of any async operations so that the then() gets triggered when the async operation is done. Then, you go on with your throw/catches.
Never use async callbacks in your Promise object. Use resolve/reject first.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function timeout(duration) { console.log(' wait ' + duration + ' milli-seconds '); return new Promise(function(resolve) { console.log('...new Promise '); setTimeout(resolve, duration); }); } // then catches the resolve timeout(3000).then(function() { console.log('...then'); throw 'worky!'; // return Promise.reject('worky'); also works }).catch(function(e) { console.log('catch: ' + e); // 'worky!' }); |
This is basically why you should not put async callbacks inside of promises.
Promise object returned at then() over-rides previous Promises.
Chaining the Promise
Let’s chain multiple Promises together.
1) The first promise is that if mom is happy she’ll give you the phone
2) Given 1), you then promise to show your friend the phone.
1)
Create the promise. If mom is happy, call resolve with the phone object.
If not happy, then call the reject with the error object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var isMomHappy = true; var willIGetNewPhone = new Promise( function (resolve, reject) { console.log("-- willIGetNewPhone --"); if (isMomHappy) { console.log("Yay! Mom is happy"); var phone = { brand: 'Apple', color: 'Red' }; resolve(phone); // fulfilled } else { console.log("Uh oh, Mom NOT happy"); var reason = new Error('mom is not happy'); reject(reason); // reject } } // Promise function ); //Promise |
2) Create the 2nd Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// create the functionality var showOff = function (phone) { // create object console.log("-- showOff --"); return new Promise( function (resolve, reject) { var message = 'Hey friend, I have a new ' + phone.color + ' ' + phone.brand + ' phone'; resolve(message); } // function ); // Promise }; // object |
Chain em together
Let’s Chain the promises together. First, WillIGetNewPhone is the first promise. It runs, and when it resolves via .then, it will pass the phone object to the 2nd promise in obj showOff.
Once showOff resolves, it will then go to its resolve function definition and do the display.
finally, don’t forget to call askMom().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// call our promise var askMom = function () { willIGetNewPhone .then(showOff) // chain it here .then(function (fulfilled) { console.log(fulfilled); // output: 'Hey friend, I have a new black Samsung phone.' }) .catch(function (error) { // oops, mom don't buy it console.log(error.message); // output: 'mom is not happy' }); }; askMom(); |
Mom, Dad example
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
var isMomHappy = true; var isDadHome = true; // CREATE the Promise var askMomForPhone = new Promise(function (resolve, reject) { console.log("askMomForPhone - new Promise created"); console.log("askMomForPhone - lets implement the code and let it know when to call resolve/reject"); if (isMomHappy) { var phone = { brand: 'Apple', color: 'Red' }; console.log("askMomForPhone - Success condition. Let's call RESOLVE"); resolve(phone); // fulfilled } else { var reason = new Error('ERROR - mom is not happy'); console.log("askMomForPhone - Unsuccessful condition (MOM NOT HAPPY). Let's call REJECT"); reject(reason); // reject } } // Promise function ); //Promise // CONSUME THE Promise var askMom = function () { console.log("askMomForPhone - Let's call object willIGetNewPhone, it then instantiate a new Promise object..."); askMomForPhone .then(function (fulfilled) { console.log("askMomForPhone - .then"); console.log(fulfilled); }) .catch(function (error) { console.log("askMomForPhone - .catch"); console.log(error.message); console.log("If mom is not happy, then let's ask DAD!!! :X "); askDad(); }); }; var askDadForPhone = new Promise(function(resolve, reject) { console.log("askDadForPhone - new Promise created"); console.log("askDadForPhone - lets implement the code and let it know when to call resolve/reject"); var phone = { brand: 'ZTE', color: 'blue' }; if(isDadHome == true && isMomHappy == false) { console.log("askDadForPhone - Success condition (MOM IS NOT HAPPY, BUT DAD IS HOME!) Let's resolve it by having Dad buy it."); resolve(phone); } else if (isMomHappy == true) { console.log("askDadForPhone - Success condition (MOM IS HAPPY, DAD MAY OR MAY NOT BE HOME.) DOESN'T MATTER, DO NOTHING."); } else { console.log("askDadForPhone - Unsuccess condition (DAD IS NOT HOME) - Let's call REJECT"); reject("ERROR - dad is not home!"); } }); var askDad = function() { askDadForPhone .then(function(phone){ console.log("-- RESOLVE --"); console.log("askDadForPhone - .then"); console.log(phone); }) .catch(function(error){ console.log("askDadForPhone - .catch"); console.log(error); }); }; console.log("\n------- Let's run this script! ---------"); askMom(); // run the promise consumption |
Similarly
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 38 39 40 41 42 43 44 45 46 47 48 49 50 |
function promise1(prev) { return new Promise((resolve, reject) => { output.push('Promise 1'); console.log("pushed promise 1 to array"); setTimeout(() => { console.log("promise 1 resolved"); resolve(output) }, 2500); }); } function promise2(prev) { return new Promise((resolve, reject) => { output.push('Promise 2'); console.log("pushed promise 2 to array"); setTimeout(() => { console.log("promise 2 resolved"); resolve(output) }, 3500); }); } function promise3(prev) { return new Promise((resolve, reject) => { output.push('Promise 3'); console.log("pushed promise 3 to array"); setTimeout(() => { console.log("promise 3 resolved"); resolve(output) }, 500); }); } let output = []; promise1(output) .then((output) => { // resolve from promise1 will call this 'then' console.log("promise 1 done, let's go promise 2"); return promise2(output); }) .then((output) => { // resolve from promise 2 will call this 'then' console.log("promise 2 done, let's go promise 3"); return promise3(output); }) .then((output) => { console.log("promise 3 done. log output"); console.log(output); // ['Promise 1', 'Promise 2', 'Promise 3'] }) .catch((error) => { // Something went wrong }); |
Waiting for all tasks to be done
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 38 39 40 41 42 43 44 |
// Instead do this function promise1(prev) { return new Promise((resolve, reject) => { output.push('Promise 1'); console.log("pushed promise 1 to array"); setTimeout(() => { console.log("promise 1 resolved"); resolve(output) }, 2500); }); } function promise2(prev) { return new Promise((resolve, reject) => { output.push('Promise 2'); console.log("pushed promise 2 to array"); setTimeout(() => { console.log("promise 2 resolved"); resolve(output) }, 3500); }); } function promise3(prev) { return new Promise((resolve, reject) => { output.push('Promise 3'); console.log("pushed promise 3 to array"); setTimeout(() => { console.log("promise 3 resolved"); resolve(output) }, 500); }); } let output = []; Promise.all([ promise1(output), promise2(output), promise3(output) ]) .then(function(output) { console.log("!! -- all done -- !!"); console.log(output); // ['Promise 1', 'Promise 2', 'Promise 3'] }); |
Promise All
Promise All runs all its promises parallel. When all of its promises are done, it will then run its resolve.
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 38 39 40 |
var max = 12; var max2 = 5; var max3 = 8; var promise1 = new Promise(function(resolve, reject){ let interval = setInterval(function(){ console.log("promise1: "+max--); if (max === 1) { resolve(1); clearInterval(interval); } }, 1000); }); var promise2 = new Promise(function(resolve, reject){ let interval2 = setInterval(function(){ console.log("promise2: "+max2--); if (max2 === 2) { resolve(2); clearInterval(interval2); } }, 1200); }); var promise3 = new Promise(function(resolve, reject) { let interval3 = setInterval(function(){ console.log("promise3: "+max3--); if (max3 === 3) { resolve(3); clearInterval(interval3); } }, 1300); }); Promise.all([promise1, promise2, promise3]).then(function(values) { console.log(values); }); // expected output: Array [1, 2, 3] |
Usages
https://medium.com/@ivantsov/promises-in-js-one-more-interview-question-6d59634a3463
Given:
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 |
function foo() { console.log('foo start'); return new Promise(resolve => { console.log('new Promise in foo'); setTimeout(() => { console.log('foo resolved...!'); resolve('foo resolved'); }, 3000); console.log('foo Promise created and returned'); }); } // foo function bar() { console.log('bar start'); return new Promise(resolve => { console.log('new Promise in bar'); setTimeout(() => { console.log('bar resolved...!'); resolve('bar resolved'); }, 4000); console.log('bar Promise created and returned'); }); } |
Example 1
1 2 3 4 5 6 7 |
foo().then(function() { return bar().then(function(result){ console.log('result: ' + result); }); }).then(function(finalResult) { console.log('finalResult: ' + finalResult); }); |
output:
foo start
new Promise in foo
foo Promise created and returned
foo resolved…!
bar start
new Promise in bar
bar Promise created and returned
bar resolved…!
result: bar resolved
finalResult: undefined
The reason why finalResult is undefined is due to:
1 2 3 4 5 6 7 8 |
foo().then(function() { return bar().then(function(result){ console.log('result: ' + result); // no return object here }); }).then(function(finalResult) { console.log('finalResult: ' + finalResult); }); |
Thus, bar() returns an object that resolves. The then() captures it and then at this point, there is nothing. Hence, we need to return another Promise object like so:
1 2 3 4 5 6 7 8 9 10 11 |
foo().then(function() { return bar().then(function(result){ console.log('result: ' + result); // returns Promise object that // resolves with value result return result; }); }).then(function(finalResult) { console.log('finalResult: ' + finalResult); }); |
in order to propagate data further onto the next then().
Errors
Handling errors is basically the 2nd parameter. Just implement a callback for it and handle the parameter of your callback.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function executePromise(i) { return new Promise(function(resolve, reject) { // do a thing, possibly async, then... if (i > 8) { resolve(i-1); } else { reject("Booo...less than 8."); } }); } executePromise(7).then(function(result){ console.log(result); }, function(err) { console.log("hoooo dayam!!!! error yo: " + err); }); |
await vs then
ref – https://dev.to/masteringjs/using-then-vs-async-await-in-javascript-2pma
await is simply syntactic sugar that replaces a .then() handler with a bit simpler syntax. But, under the covers the operation is similar.
The code that comes after the await (that is within the async function) is basically put inside an invisible .then() handler.
JavaScript will pause the async function’s execution until the promise settles. All code that is after the await will wait (block) until the await settles.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const waitFiveSeconds = () => { return new Promise((resolve, reject) => { console.log('Promise done') setTimeout(() => { resolve(88); }, 5000) }) } async function test() { console.log('Ready'); const result= await waitFiveSeconds(); // code here will wait until waitFiveSeconds settles console.log('I will print second: ' + result); console.log('end of function test') } test(); // async operation console.log('I will print first'); |
With then(), the rest of the function will continue to execute just like how async/await lets all code below the async function run. When the Promise settles, the then() will then executes. So in other words, all code below await, is very similar to having a then.
Therefore, await is just an internal version of .then() (doing basically the same thing). The reason to choose one over the other doesn’t really have to do with performance, but has to do with desired coding style or coding convenience. Certainly, the interpreter has a few more opportunities to optimize things internally with await, but it’s unlikely that should be how you decide which to use. If all else was equal, I would choose await for the reason cited above. But, I’d first choose which made the code simpler to write and understand and maintain and test.
Used properly, await can often save you a bunch of lines of code making your code simpler to read, test and maintain. That’s why it was invented.
There’s no meaningful difference between the two versions of your code. Both achieve the same result when the axios call is successful or has an error.
Where await could make more of a convenience difference is if you had multiple successive asynchronous calls that needed to be serialized. Then, rather than bracketing them each inside a .then() handler to chain them properly, you could just use await and have simpler looking code.