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
onFulfilled
is not a function andpromise1
is fulfilled,promise2
must be fulfilled with the same value aspromise1
.If
onRejected
is not a function andpromise1
is rejected,promise2
must 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
onFulfilled
oronRejected
returns 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.
onFulfilled
oronRejected
must not be called until the execution context stack contains only platform codeIn practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously, after the event loop turn in whichthen
is called, and with a fresh stack. This can be implemented with either a āmacro-taskā mechanism such assetTimeout
orsetImmediate
, or with a āmicro-taskā mechanism such asMutationObserver
orprocess.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)
. Ifx
is a thenable, it attempts to makepromise
adopt the state ofx
, under the assumption thatx
behaves at least somewhat like a promise. Otherwise, it fulfillspromise
with 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
x
is a promise, adopt its state
- If
x
is pending,promise
must remain pending untilx
is fulfilled or rejected.- If/when
x
is fulfilled, fulfilpromise
with the same value.- If/when
x
is rejected, rejectpromise
with 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
resolvePromise
andrejectPromise
are 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
promise
andx
refer to the same object, rejectpromise
with aTypeError
as the reason.
When x === p2, it would cause dead loop: p2.then().then()
1 | if(x === promise2) { |
If
x
is not an object or function, fulfillpromise
withx
.
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) { |