Promise/A + specification
The specification provides several important messages:
-
Promise is a state machine
-
** THEN ** method has two parameters: ** Promise. Then (ondepressing, onRejected)**, where:
onFulfilled
和onRejected
Are optional arguments that must be ignored if they are not functions- when
onFulfilled
It’s a function- This must be implemented after the state of the promise changes to a big pity, and the promise value is taken as the first parameter
- Can be executed only once
- when
onRejected
It’s a function- Must be executed after the promise status changes to Rejected, with the Promise value as the first parameter
- Can be executed only once
- OnFulfilled or onRejected must not be called until the execution context stack contains only platform code. (Don’t understand this paragraph, probably support asynchronous?)
then
This can be fulfilled many times. After the state of promise changes to pity/Rejected,**onFulfilled
* * and**onRejected
** is executed in the order in which it was originally bound.then
You must return a promisepromise2 = promise1.then(onFulfilled, onRejected)
- whenonFulfilledoronRejectedReturn a value x, and run the Promise resolver
[[Resolve]] (promise2, x)
- When onFulfilled or onRejected throws exception E, promise2 must change to Rejected and regard E as reason
- when
**onFulfilled
** is not a function, and the state of promise1 is a big pity. Promise2 must perform the state transition with the same value. - when
**onRejected
** is not a function and the state of promise1 is Rejected. Promise2 must use the same reason to complete the state transition.
- whenonFulfilledoronRejectedReturn a value x, and run the Promise resolver
-
[[Resolve]] (promise2, x promise
The Promise resolution process is an abstract operation that takes a Promise and a value as input, which we express as [[Resolve]](Promise, x). In general, if x is thenable, which includes the then method, it will force a Promise to adopt x’s state on the assumption that X at least behaves like a Promise. Otherwise, the promise will take the value X fulfill.
Perform the following steps for details
- If x and promise point to the same object, then
TypeError
As a reason, reject, promise - If x is a promise, adopt its state
- If x is object or function
- let then = x.then
- If retrieving the attribute X.teng causes an exception E to be thrown, reject the promise because e.
- If then is a function, x is called as this, with the first argument resolvePromise and the second argument rejectPromise
- If the resolvePromise is called with the value y, run **
[[Resolve]] (promise, y)
阿鲁纳恰尔邦 - Reject a rejectPromise with r if it is invoked using reason R
- If both resolvePromise and rejectPromise are called, or if multiple calls are made to the same parameter, the first call takes precedence and any other calls are ignored.
- If an exception occurs in the THEN call
- If resolvePromise or rejectPromise has been invoked, ignore the exception
- Otherwise, reject the promise for reason E.
- If the resolvePromise is called with the value y, run **
- If x and promise point to the same object, then
Now that the PromiseA+ specification is clear, let’s start implementing it step by step.
Implementation framework
The Promise constructor takes a function as an argument, resolve and reject. They are two functions provided by the JavaScript engine, so here we first write out the constructor skeleton. Resolve and Reject are the parts that need to be implemented internally. The resolve function changes the state of the Promise object from “unfinished” to “successful” (from pending to Resolved), and the Reject function changes the state from “resolved” to “resolved”. Change the state of the Promise object from “Unfinished” to “Failed” (from Pending to Rejected).
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Mypromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
const resolve = (value) = > {
this.value = value
this.state = FULFILLED
}
const reject = (reason) = > {
this.reason = reason
this.state = REJECTED
}
try {
executor(resolve, reject)
}catch (reason){
reject(reason)
}
}
}
Copy the code
Implement then methods
The then method takes two functions, provides delayed binding, and supports multiple calls, so we need to store the callbacks internally in two arrays, so that resolve and reject can be executed by pulling the callbacks out of the arrays.
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Mypromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
this.onFullfilledCallbacks = []; // Save the successful callback
this.onRejectedCallbacks = []; // Save the failed callback
const resolve = (value) = > {
this.value = value
this.state = FULFILLED
// Take the callback functions from the successful callback array and execute them one by one.
this.onFullfilledCallbacks.forEach(onFullfilledCallback= > {
onFullfilledCallback(this.value)
})
}
const reject = (reason) = > {
this.reason = reason
this.state = REJECTED
// Take the callback functions from the failed callback array and execute them one by one
this.onRejectedCallbacks.forEach(onRejectedCallback= > {
onRejectedCallback(this.reason)
})
}
try {
executor(resolve, reject)
}catch (reason){
reject(reason)
}
}
then(onFulfilled, onRejected) {
if (this.state === FULFILLED) {
onFulfilled(this.value)
}else if (this.state === REJECTED) {
onRejected(this.reason)
}else {
this.onFullfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected); }}}Copy the code
The above constructor ensures delayed binding of the callback functions and ensures that the callback functions are used in binding order. All promises are the same, except for the timing of resolve or Reject determined by executor, the executor function that executes immediately. Therefore, the following then method returns the new promisE2 with the same idea, making the executor of the new promise2 strongly relevant to the current promise.
The then method returns a promise
then(onFulfilled, onRejected) {
console.log('then called')
const promise2 = new MyPromise((resolve, reject) = > {
// If the current promise state is pending, wrap the callback into an array,
// After the promise state transitions, the corresponding callback function is invoked, and the return value is collected to begin parsing promise2
if (this.state === PENDING) {
this.onFullfilledCallbacks.push(() = > {
try {
let x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
}catch(error) { reject(error); }})this.onRejectedCallbacks.push(() = > {
try {
let x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
}catch(error) {
reject(error)
}
})
}
else if (this.state === FULFILLED) {
try {
let x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
}catch (error) {
reject(error)
}
}
else if (this.state === REJECTED) {
try {
let x = onRejected(this.value);
this.resolvePromise(promise2, x, resolve, reject);
}catch (error) {
reject(error)
}
}
})
return promise2;
}
Copy the code
Promise parsing function
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining Cycle'));
}
if (x && typeof x === 'object' || typeof x === 'function') {
let used;
try {
let then = x.then;
// x. teng is function. X is a promise
if (typeof then === 'function') {
then.call(x, (y) = > {
if (used) return;
used = true;
this.resolvePromise(promise2, y, resolve, reject)
}, (r) = > {
if (used) return;
used = true; reject(r); })}else {
if (used) return;
used = true; resolve(x); }}catch (error) {
if (used) return;
used = true
reject(error)
}
} else{ resolve(x); }}Copy the code
Complete code example
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
this.PID = Math.random(); // It is useless for debugging
this.value = null
this.reason = null
this.state = PENDING;
this.onFullfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
this.value = value
this.state = FULFILLED
this.onFullfilledCallbacks.forEach(onFullfilledCallback => {
onFullfilledCallback(this.value)
})
console.log('Promise state is ' + this.state)
}
const reject = (reason) => {
this.reason = reason
this.state = REJECTED
this.onRejectedCallbacks.forEach(onRejectedCallback => {
onRejectedCallback(this.reason)
})
console.log('Promise state is ' + this.state)
}
try {
executor(resolve, reject)
}catch (reason){
this.reject(reason)
}
}
then(onFulfilled, onRejected) {
console.log('then called')
const promise2 = new MyPromise((resolve, reject) => {
// If the current promise state is still pending, wrap the callback function into the array and wait until the promise state is changed
// Invoke the appropriate callback function and begin parsing promise2
if (this.state === PENDING) {
this.onFullfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
}catch(error) {
reject(error);
}
})
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
}catch(error) {
reject(error)
}
})
}
else if (this.state === FULFILLED) {
try {
let x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
}catch (error) {
reject(error)
}
}
else if (this.state === REJECTED) {
try {
let x = onRejected(this.value);
this.resolvePromise(promise2, x, resolve, reject);
}catch (error) {
reject(error)
}
}
})
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining Cycle'));
}
if (x && typeof x === 'object' || typeof x === 'function') {
let used;
try {
let then = x.then;
// x. teng is function. X is a promise
if (typeof then === 'function') {
then.call(x, (y) => {
if (used) return;
used = true;
this.resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if (used) return;
used = true;
reject(r);
})
} else {
if (used) return;
used = true;
resolve(x);
}
}catch (error) {
// If resolvePromise or rejectPromise has been called, ignore the exception
if (used) return;
used = true
reject(error)
}
} else {
resolve(x);
}
}
// The last catch is onRejected
catch(onRejected) {
return this.then(null, onRejected);
}
}
Copy the code