Disclaimer
This article is originally based on my understanding of this concept. No guarantee on the accuracy.š
[toc]
Promise A+ step by step
A promise represents the eventual result of an asynchronous operation
State Machine
States: āpendingā;āfulfilledā;ārejectedā;
1 | const PENDING = 'pending'; |
executor
executor will be executed immediately, it takes two function as parameters
1 | function Promise(executor){ |
resolve & reject function
these two function are to
- pass the async results outside for chaining which avoids
nested callback - change the state of current promise instance;
Only when the state is PENDING, a transition can be made;
1 | const resolve=(value)=>{ |
then
chain revoke => Promise.prototype.then(onRes,onRej)
Methods in then can obtain the value passed from the promise instance => use this =>pass the value/reason out by passing them into the resolve/rejection function
1 | Promise.prototype.then = function(onFulfilled, onRejected){ |
1 | //constructor: |
then() returns a new promise instance
1 | const promise2 = new Promise((resolve, reject) => {}) |
the new promise instance, can keep calling then on itself and pass the previous value/reason to the next then by passing it to the resolve and reject functions of itself.
If
onFulfilledis not a function andpromise1is fulfilled,promise2must be fulfilled with the same value aspromise1.If
onRejectedis not a function andpromise1is rejected,promise2must be rejected with the same reason aspromise1.
the result (value/reson) should āpenetrateā to the next then.
Providing a default function: (value)=>value
1 | onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; |
so when onFulfilled /onRejected are not function , promise2:
1 | //let x=onFulfilled(this.value)==>this.value |
1 | const promise2 = new Promise((resolve, reject) => { |
the onFullfilled(this.value) and onRejected(this.reason) can possibly create error, wrap it with try and catch;
If either
onFulfilledoronRejectedreturns a valuex, run the Promise Resolution Procedure[[Resolve]](promise2, x).
eg. p.then((v)=>v.data) x==>v.data
Encapsulate the logic in a new function resolvePromise.
1 | if(this.state === FULFILLED){ |
this.state===PENDING
if the promise isnāt resolved when the then() has already been invoked, how to obtain the value/reason from the promise after it was fulfilled/rejected to run the onFullfilled/onRejected function?
Store the callbacks in the constructor.
1 | this.onResolvedCallback = []; |
1 | const resolve=(value)=>{ |
and add the pending call back in the then() method and introduce the setTimeout to ensure the callback can be invoked asynchronously.
onFulfilledoronRejectedmust not be called until the execution context stack contains only platform codeIn practice, this requirement ensures that
onFulfilledandonRejectedexecute asynchronously, after the event loop turn in whichthenis called, and with a fresh stack. This can be implemented with either a āmacro-taskā mechanism such assetTimeoutorsetImmediate, or with a āmicro-taskā mechanism such asMutationObserverorprocess.nextTick.
1 | if(this.state === PENDING){ |
The Promise Resolution Procedure : resolvePromise()
The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as
[[Resolve]](promise, x). Ifxis a thenable, it attempts to makepromiseadopt the state ofx, under the assumption thatxbehaves at least somewhat like a promise. Otherwise, it fulfillspromisewith the valuex.
To implement the functionality of the [[Resolve]](promise, x), the resolvePromsie() takes the new promise promise2, the returned value x, and the resolve and reject function of new promise as parameters
If
xis a promise, adopt its state
- If
xis pending,promisemust remain pending untilxis fulfilled or rejected.- If/when
xis fulfilled, fulfilpromisewith the same value.- If/when
xis rejected, rejectpromisewith the same reason.
The promise2 will adopt/wait for x until it was fulfilled/rejected; āAdoptā means to call the resolve and reject function of its own with the results of promise x.
Theses are the same behaviour of a then method of a promise/thenable object.
If both
resolvePromiseandrejectPromiseare called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
Use a flag called to mark the first called function
Here we treat the promise and thenable object using same function:
1 | if(x && typeof x === 'object' || typeof x === 'function'){ |
Avoid dead loop:
If
promiseandxrefer to the same object, rejectpromisewith aTypeErroras the reason.
When x === p2, it would cause dead loop: p2.then().then()
1 | if(x === promise2) { |
If
xis not an object or function, fulfillpromisewithx.
1 | else{ |
The final version of resolvePromise:
1 | function resolvePromise(promise2, x, resolve, reject) { |
The constructor and prototype of Promise:
1 | const PENDING = 'pending'; |
1 | Promise.prototype.then = function (onFulfilled, onRejected) { |