The quiz about promise
PromiseA + specification
let p = new Promise((resolve,reject) = >{
try{... resolve(val) }catch(err) {
reject(err)
}
})
p.then((val) = >{... })Copy the code
In basic usage, the basic structure of promises should look like this
class Promise{
/ / the constructor
constructor(executor){
let resolve = (a)= >{};let reject = (a)= >{}; executor(resolve, reject); } then(onFulfilled,onRejected){} }Copy the code
Explain:
- Promise accepts a function as an argument, which in the specification is called executor
- The executor function takes two arguments, resolve and reject, which are actually defined inside the Promise function. See below for details
The specification largely represents the design principles of Promise, and we refined it step by step according to the A+ specification. You may have questions about some of these implementations along the way, but don’t worry, wait until the full implementation is complete and you’ll see
The state of the Promise
- This is a big pity, which can be changed into a pity (failure) and rejected (failure).
- This is a big pity: The successful state cannot be changed into other states, and there must be an unchangeable value.
- The Rejected state cannot be converted into other states and there must be an unchangeable reason.
promise1.0
class MyPromise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
let resolve = value= > {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value; }};let reject = reason= > {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason; }};// If the executor executes incorrectly, execute reject
try{
executor(resolve, reject);
} catch(err) { reject(err); }}}Copy the code
Then method
- A promise must provide one
then
Method to access its current value, final value, and cause. The promisethen
The method takes two arguments:
promise.then(onFulfilled, onRejected)
Copy the code
then
The method can be the samepromise
Call several times
This is a big pity. When the state is fulfilled, onFulfilled. When the state is fulfilled, onFulfilled.
class MyPromise {
constructor(executor){... } then(onFulfilled,onRejected) {if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
if (this.state === 'rejected') {
onRejected(this.reason); }; }}Copy the code
Implementing asynchronous calls
This has implemented a basic prototype of a promise, but it does not yet allow for asynchronous calls, such as:
new MyPromise((resolve,reject) = >{
setTimeout((a)= >{
console.log(123)
resolve()
},100)
}).then((a)= >{
console.log(456)})Copy the code
You’ll notice that the output is also not as expected, because resolve is executed asynchronously, so resolve occurs after THEN, and state remains pendding. So we made the following transformation
class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value= > {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn= >fn()); // }};let reject = reason= > {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn= >fn()); }};try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
if (this.state === 'rejected') {
onRejected(this.reason);
};
// Store both functions when state is pending
if (this.state === 'pending') {
this.onResolvedCallbacks.push((a)= >{
onFulfilled(this.value);
})
this.onRejectedCallbacks.push((a)= >{
onRejected(this.reason); })}}}Copy the code
Following the idea of publish and subscribe, use an array to store the two functions in then, which will be executed when an asynchronous task starts. Why use arrays? Since a promise can call more than one THEN, we push the parameters into an array each time we call then, and foreach executes them in resolve or reject, for example:
// Multiple THEN cases
let p = new Promise(a); p.then(); p.then();Copy the code
In addition, we must ensure that the two function parameters onFulfilled and onRejected in THEN are executed last, so we should put them in the asynchronous queue. The implementation method is to wrap them with setTimeout
. if (this.state === 'pending') {
this.onResolvedCallbacks.push((a)= >{
setTimeout((a)= >{
onFulfilled(this.value);
},0)})this.onRejectedCallbacks.push((a)= >{
setTimeout((a)= >{
onRejected(this.reason);
},0)})}Copy the code
Chain calls
Promise’s greatest use for chain-calling is to solve the problem of callback hell. The chain invocation implementation is then implemented by returning a promise in the then function, i.e
promise2 = promise1.then(onFulfilled, onRejected);
Copy the code
According to A+ specification:
- if
onFulfilled
oronRejected
Return a valuex
, run the followingPromise resolution process:[[Resolve]](promise2, x)
- if
onFulfilled
oronRejected
Throw an exceptione
,promise2
Execution must be rejected and a rejection must be returnede
- if
onFulfilled
Is not a function andpromise1
Successful execution,promise2
Must execute successfully and return the same value - if
onRejected
Is not a function andpromise1
Refuse to enforce,promise2
Execution must be rejected and the same data returned
By specification, we should compare x and promise, so we should implement a resolvePromise(promise,x,resolve,reject), so the then method implementation should look like this:
...let promise2 = new Promise((resolve, reject) = >{
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value);
// the resolvePromise function handles the relationship between its return promise and the default promise2
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'rejected') {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push((a)= >{
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.onRejectedCallbacks.push((a)= >{
let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }}}));// Return promise, complete the chain
returnpromise2; }}Copy the code
The following is mainly the implementation process of resolvePromise: there are three cases stipulated in the specification: X is equal to promise; X as the promise; X is an object or a function. Promise resolution is an abstract operation that takes a Promise and a value, which we represent as [[Resolve]](Promise, x). To run [[Resolve]](Promise, x), follow these steps: Refer to the Promise settlement process. X is equal to a Promise. If a promise and x refer to the same object, reject the promise as TypeError.
- X to Promise
- If x is a Promise, make the Promise accept the state of X.
- If X is in wait state, the promise needs to remain in wait state until x is executed or rejected.
- If x is in the execution state, execute the promise with the same value.
- If X is in the reject state, reject the promise with the same grounds.
- X is an object or function
If x is an object or function:
- Assign x. teng to then.
- If an error e is thrown when taking the value x. teng, reject the promise based on e.
- If then is a function, x is called as the function’s scope this. Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise:
- If resolvePromise is called with the value y, run [[Resolve]](promise, y)
- If rejectPromise is invoked with argument r, reject the promise with argument r
- If both resolvePromise and rejectPromise are invoked, or if the same parameter is invoked more than once, the first call is preferred and the remaining calls are ignored
- If calling the then method raises exception e:
- If a resolvePromise or rejectPromise has already been invoked, it is ignored
- Otherwise, reject the promise based on e
- If then is not a function, execute the promise with an x argument
- If x is not an object or function, execute the promise with x as an argument
If a promise is resolved by an object in a loop’s Thenable chain, and the recursive nature of [[Resolve]](promise, thenable) causes it to be called again, the algorithm above will lead to infinite recursion. The algorithm does not require it, but encourages the agent to detect the presence of such recursion, and if so, reject the promise with an identifiable TypeError as a justification.
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('Circular reference'));
}
if (x instanceof Promise) {
if (x.state === PENDING) {
x.then(
y= >{ resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); }); }else{ x.then(resolve, reject); }}else if (x && (typeof x === 'function' || typeof x === 'object')) {
// Avoid multiple calls
let called = false;
try {
// assign x. teng to then
let then = x.then;
if (typeof then === 'function') {
// If then is a function, x is called in the function's scope this.
// Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise
// If both resolvePromise and rejectPromise are called, or if the same parameter is called more than once, the first call is preferred and the remaining calls are ignored
then.call(
x,
// If resolvePromise is called with the value y, run [[Resolve]](promise, y)
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
// If rejectPromise is invoked with argument r, reject the promise with argument r
r => {
if (called) return;
called = true; reject(r); }); }else {
// If then is not a function, execute a promise with an x argumentresolve(x); }}catch (e) {
// If an error e is thrown when taking the value x. teng, reject a promise based on e
// If calling the then method throws an exception e:
// If resolvePromise or rejectPromise has already been invoked, ignore it
// Otherwise use e as the basis for rejecting the promise
if (called) return;
called = true; reject(e); }}else {
// If x is not an object or function, execute a promise with x as an argumentresolve(x); }}Copy the code
Finally, we’ll complete the rest of the Promise API:
Promise.reject = function(val){
return new Promise((resolve,reject) = >{
reject(val)
});
}
/ / race method
Promise.race = function(promises){
return new Promise((resolve,reject) = >{
for(let i=0; i<promises.length; i++){ promises[i].then(resolve,reject) }; })}//all (get all promises, execute then, place the results in an array, and return them together)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data,resolve){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject) = >{
for(let j=0; j<promises.length; j++){ promises[i].then(data= >{
processData(j,data,resolve);
},reject);
};
});
}
Copy the code
Reference: juejin. Cn/post / 684490… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490…