2020-05-16

Understanding Promise part 2

Disclaimer

This article is originally based on my understanding of this concept. No guarantee on the accuracy.😅

‘The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.’

It was introduced to handle asynchronous operation like synchronous opeartion and avoid the callback hell.

1
2
3
4
5
6
7
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);

Common asynchronous-operation

  • setTimeOut, setInterval
  • XHR request (fetch api)
  • Event Listening

Thansk to promise’s .then() chaining, we can put the callback outside:

1
2
3
4
5
6
7
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);//error handler for all above!

*This all based on modern API that returning a promise. That is to say, the promise chains starts with an API function which returns a promise. *

We can create our own promise as well. Although it’s not typical practice but it can be helpful to ‘promisify’ some old callback-style API then chain the handler.

Let’s look at how a promise is created.

1
2
3
4
5
6
7
8
9
10
11
12
var promise = new Promise(
//executor which runs immediately/sync:
console.log('sync!')
(res, rej)=>{
// asynchronous operation. they will be put into the task queue
//instead of returning something, it calls the callback as sideeffect to pass the result of asyn operation to res()/rej()
if(somecodition) res(result); //once the promise is settled the state won't be changed
rej(error)
//then the res()/rej() can pass the result to the ousdie by chaining a .then()/.catch()
}
//the construcor return the new promise obj (of course)
);

An example of wrapping a callback-style function with Promise:

1
2
3
4
5
6
7
8
9
10
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);
//template
new Promise(function (resolve, reject) {
resolve(someSynchronousValue);
}).then( /* ... */ );
//more simple and clear way
Promise.resolve(someSynchronousValue).then(
/* recieve someSynchronousValue return value*/
);

Another example to implement the query cache:

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
function query(name) {
//use property of function to cache, use map datastructure
const cache = query.cache || (query.cache = new Map());
if (cache.has(name)) {
console.log('get data from cache');
return Promise.resolve(cache.get(name));
}
return mockAjax(name).then(
(user) => {
cache.set(name, user);
console.log('not in the cache');
return user;
},
(error) => console.log(error)
);
}
//a mock ajax request
function mockAjax(name) {
return new Promise((res, rej) => {
let a = Math.random();
if (a < 0.5) {
res('user:' + name);
} else {
rej('not found');
}
});
}
//to obtain the user outside the

Promise prototype method

  • Promise.prototype.then()
  • Promise.prototype.catch()
  • Promise.prototype.finally()

Async & await

ES2017 🍬🍬🍬

1
2
3
4
5
6
7
8
9
10
async function foo() {
try {
const result = await doSomething();
const newResult = await doSomethingElse(result);
const finalResult = await doThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
}

async alone syntax can be used to create promise as well:

1
2
3
4
5
6
7
8
async function foo(){
//async opeartion here....
//instead of resolve/reject, just return the result form async operation
return value
//for error
throw error
}
let p1 = foo() //Promise Promise {<resolved>: value} /error

await has to be used inside a async function.

await + < Promise > will wait the promise to be settled

Re-write the query search function in async await:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function mockAjax(name) {
let a = Math.random();
if (a < 0.5) {
return 'user:' + name;
} else {
throw 'not found';
}
}
async function query(name) {
const cache = query.cache || (query.cache = new Map());
if (cache.has(name)) {
console.log('get data from cache');
return cache.get(name);
}
try {
let user = await mockAjax(name);
cache.set(name, user);
console.log('not in the cache');
return user;
} catch (error) {
console.error(error);
}
}

Promise queue implementation

  • using forEach
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function doThing(fn, callback) {
fn(); //call the funcion in the list
callback(); //call the resolve to pass it to next promise
}

function queue(lists) {
let promise = Promise.resolve();
lists.forEach(fn => {
promise = promise.then(() => { //key point here, let promise point to next promise
return new Promise(resolve => {
doThing(fn, () => {
resolve()
});
});
});
});
return promise; //return the promise
}

using reduce

1
2
3
4
5
6
7
8
9
10
11
function queue(lists) {
return lists.reduce((promise, list) => {
return promise.then(() => {
return new Promise(resolve => {
doThing(list, () => {
resolve;
});
});
});
}, Promise.resolve());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function f2() {
setTimeout(() => {
console.log(2)
}, 200)
}

function f1() {
console.log(1)
}

function f3() {
console.log(3)
}

queue([f1, f2, f3])

Other promise API

  • Promise.resolve()

    • This one actually can be used to implement the .then() and .catch() api
    • If the none parameter, return a resovled Promise with undefined value
    • if the parameter is not a promise object, return a resovled Promise with that parameter
    • If the parameter is a promise object, return that promise as it is.
  • Promise.reject()

    • rarely used
  • Promise.race()

  • Promise.all(< iterable >)

    • usually work with .map()

    • p.then(results=>{
          return Promise.all(results.map(r=> new Promise(resolve=>{
              ...//do something
          })))
      })

Reference:

MDN Promise