The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
It is used for getting some data from the callback function in some determined function. Promise.all, or .then.
content talbe:
- Creating a promise
- Using a promise for getting data from two and more callbacks
- How to use a promise in a loop
- How to handle promise rejection inside async functions
Things that you should remember:
resolve and reject are both return promises, so when you do resolve/reject to interupte the function execution you have to use return resolve or return reject
#Creating a promise
Creating a promise is very simple, we use constructor Promise and inside pass, a callback function with two parameters resolve and reject
The promise should be wrapped in a function. It will work as a closure. Without wrapping we can’t use a promise over a loop, or to pass there some variable that will afterward change by upcoming code. So a simple promise definition should be:
1 2 3 4 5 6 7 8 |
var my_first_promise = function(){ return new Promise((resolve, reject) => { setTimeout(function(){ // resolve('Hello world') reject('some_error') }); }); }; |
to get a result from a promise we use special functions resolve and reject, where resolve data will be passed to a .then block and reject data will be passed to a .catch
1 2 3 4 5 |
my_first_promise().then(function(success){ console.log(`success = ${success}`); }).catch(function(error){ console.log(`error = ${error}`); }) |
#Using a promise for getting data from two and more callbacks
This is a common use case the most frequently used. Assume we have two functions f1 and f2
1 2 3 4 5 6 7 8 9 |
function f1(fn){ let ob = {name:'object1'}; setTimeout(fn, 0, ob); } function f2(fn){ let ob = {name:'object2'}; setTimeout(fn, 0, ob); } |
now we can reach each object inside each function one by one
1 2 3 4 5 6 7 |
f1(function(obj1){ console.log(obj1.name); }); f2(function(obj2){ console.log(obj2.name); }); |
but what shall we do if we want to reach obj1 and obj2 simultaneously? In this case, promises are coming to help us. We have to wrap every callback function to a promise and handle it in some special function Promise.all() that accepts an array of promises.
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 |
var pr1 = function(){ return new Promise((resolve, reject) => { f1(function(obj1){ resolve(obj1); }) }) } var pr2 = function(){ return new Promise((resolve, reject) => { f2(function(obj2){ resolve(obj2); }) }) } var p1 = pr1(); var p2 = pr2(); Promise.all([p1, p2]).then(values => { let obj1 = values[0]; let obj2 = values[1]; console.log(`obj1.name = ${obj1.name}, obj2.name = ${obj2.name}`); // obj1.name = object1, obj2.name = object2 }).catch(err => { console.log(`err = ${err}`); }) |
the output
obj1.name = object1, obj2.name = object2
#How to use a promise in a loop?
Yes, we can use promise in the loop, assume we have a contrived task to calculate something in our promises in for and pass to promise counter i. Each promise have to have a unique i. and then we have got results from all promises and output sum. Simple task
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 |
// a function that accepts a callback function function f(fn){ let n = 10; setTimeout(fn, 0, n); } // a promise function function pr(i){ return new Promise((resolve, reject) => { f(function(n){ // console.log(`i = ${i}`); resolve(i * n); }) }) } let prs = []; for (let i = 0; i < 10; i++){ prs.push(pr(i)); } Promise.all(prs).then(values => { console.log(values); // [ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 ] let sum = 0; for(let i = 0, max = values.length; i < max; i++){ sum += values[i]; } console.log(`sum = ${sum}`); // 450 }).catch(err => { console.log(`err = ${err}`); }) |
as you see promises successfully helped us to solve such a difficult task.
#How to handle promise rejection inside async functions
Case 1 The handle promise inside an async function.
We can handle such a promise by using try/catch
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 |
// the f2 async function doesn't has a catch handler // that allows users to specify own catch to handle const f2 = async () => { return new Promise((resolve, reject) => { reject('reject f2') }) } const f1 = async () => { const pr = () => { return new Promise( async (resolve, reject) => { reject("reject pr") }) .catch(e => { console.log('caught 1'); }) } try { await pr() await f2() // without await the try catch will not work } catch(e) { console.log('caught from try'); } console.log('here we are'); } f1() |
Important: the libraries like puppeteer doesn’t has in their async functions API catch handlers. You have to do like this
1 2 3 4 5 6 7 |
try { const page2 = await browser.newPage() await page2.goto(url2) await page2.waitFor('selector: div.column-search-panel__center > input') } catch(e) { console.log('caught'); } |
when you specify your own promise inside an async function you have to add try catch there too
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 |
const getData = async (number, accreditation) => { try { const browser = await puppeteer.launch({ headless: false, slowMo: 250 }) const pr1 = () => { return new Promise( async(resolve, reject) => { // here should be try catch block // without here will be unhandled rejections // because of puppeteer functions is an async // and when they will rejected nothing will // handle them (or you have to attach .catch) try { const page2 = await browser.newPage() await page2.goto(url2) await page2.waitFor('selector: div.column-search-panel__center > input') } catch(e) { console.log('caught'); } }) } await pr1() } catch (e) { console.log('caught'); } } |
so always use try/catch especially with third party libs
#How to use Promise.race properly
Promise.race allows us to put on waiting a few promises and return the result of one the first.
we use it often with puppeteer when we need to know what the DOM state is Here is an example from puppeteer how to wait for needed selector. The example below show how it works. You have to change setTimeout time to allow promises either pr1 or pr2 to be the first. The less time the first promise.
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 |
const pr1 = () => { return new Promise(async (resolve, reject) => { setTimeout(() => { resolve('[resolve]') }, 1000) }) } const pr2 = () => { return new Promise(async (resolve, reject) => { setTimeout(() => { reject('[reject]') }, 2000) }) } const getData = async () => { try { // Promise.race allows one the first promise resolve or reject const res = await Promise.race([ pr1(), pr2() ]).catch(e => { // this is code will run when the first promise is rejected // without this block the execution will be passed to the // try/catch console.log('[caught reject]'); return e }) console.log('res = ', res); console.log('here we are'); } catch(e) { console.log('e = ', e); } } getData() |
the end