preface

Asynchronous behavior is the foundation of JavaScript, but previous implementations were not ideal. In the early days of JavaScript, it was only possible to define a callback function to indicate the completion of an asynchronous operation. Concatenating multiple asynchronous operations is a common problem that often requires deeply nested callbacks (known as “hell callbacks”) to solve.

To address hell callbacks, ES6 implements Promises based on the Promises/A+ specification that Promise asynchronous behavior as A flow of synchronous behavior.

Since ES6 was released in 2015, promises have been used everywhere in development. You might have been asked about the concept and usage of a Promise in an advanced front end interview three years ago. Now it’s common to ask you to implement a Promise. This will not only test your knowledge of the concept and usage of Promise, but also your knowledge of ES6 grammar and its applications.

To implement A Promise, follow the Promise/A+ specification, which all Promise libraries in the industry follow. Of course, the specification is also a little bit according to the business scenario, this column from the business scenario, take you step by step to achieve a Promise, the specification is not good memory, business scenarios do more is not good memory.

I hope this column will give you a bit of confidence in the middle and upper end of the interview.

I. Analyze Promise’s business scenarios

Let’s start with a brief analysis of Promise’s business scenario.

  • This is very depressing. This is very depressing. This is very depressing. The outside world can’t change these three states, and once they change, they can’t change again.

This column, for the sake of convenience, will say that the state Pending is Fulfilled, the state Fulfilled is Fulfilled and the state Rejected is Fulfilled.

  • To instantiate a Promise, you pass an Executor function in which the business code is executed and the executor function takes two arguments, resolve and reject. Resolve and reject are built-in functions of the Promise constructor.

    new Promise((resolve, reject) =>{ //... Business code})Copy the code
  • Call the resolve function in the Executor function to change the Promise’s state to successful, and pass the result of the successful execution of the business code to the Promise as an argument.

  • When the code fails in the Executor function, call the Reject function, reject the Promise and pass the reason for the failure to the Promise with a parameter.

  • The first parameter of the instance method THEN is the callback function for the successful execution of the business code, and the second parameter is the callback function for the failure of the execution of the business code. After the execution of the business code is completed, the corresponding callback function will be called according to the execution result, and these callback functions receive the execution result of the business code as a parameter.

  • The instance method catch is used to add a callback function when the business code fails to execute.

Here’s how to implement promises.

2. Build the initial Promise constructor

Following the business scenario analysis above for a Promise, the constructor for a Promise should have something like this:

  • Promise is a Class, created in ES6 using the Class syntax.

  • The Promise constructor takes the Executor function as an argument and executes the Executor function within it.

  • The Promise constructor has the resolve and Reject built-in methods and is passed as arguments to the executor function.

  • Set the instance property status to store the state.

  • The built-in resolve function changes the state to succeeded, and the built-in reject function changes the state to failed and stays the same once the state changes.

This will be Fulfilled someday and will be Fulfilled someday. In addition, use Symbol outside the constructor to create three states Pending, Fulfilled and Rejected.

// Use Symbol to define three states to prevent the outside world from changing the state. const Pending = Symbol('Pending'); This will be a pity. // this will be a pity. Const Rejected = Symbol('Rejected'); Class Promise {constructor(executor){this.status = Pending; // Store the Promise state const resolve = () =>{// Only change if the Promise state is Pending, to ensure that once the state changes, it will not change again. if(this.status === Pending){ this.status = Fulfilled; }}; Const reject = () =>{// Changes only when the state is Pending, ensuring that once the state changes it will not change again. if(this.status === Pending){ this.status = Rejected; }}; executor(resolve,reject); }},Copy the code

Three, preliminary implementation of then instance method

Following the simple analysis of the business scenario for the THEN instance method above, when the callback function is called in the THEN instance method and the result of the execution of the business code in the Executor function is passed as a parameter, the instance attribute is added to store the result of the execution of the business code. The result of a successful execution is passed in as a parameter to the built-in method resolve, and the reason for its failure is passed in as a parameter to the built-in method resolve.

Const Pending = Symbol('Pending'); const Pending = Symbol('Pending'); This will be a pity. // this will be a pity. Const Rejected = Symbol('Rejected'); Class Promise {constructor(executor) {this.status = Pending; // Store the Promise state this.value = undefined; // Store the result of successful execution of the business code in the executor function this.reason = undefined; Const resolve = value => {// Resolve only if the state is Pending, to ensure that once the state changes, it will not change. if (this.status === Pending) { this.status = Fulfilled; this.value = value; }}; Const reject = value => {// Reject only when the state is Pending, ensuring that once the state changes it will not change again. if (this.status === Pending) { this.status = Rejected; this.reason = value; }}; executor(resolve, reject); } then(onFulfilled, onRejected) { if(this.status === Fulfilled){ if (onFulfilled && typeof onFulfilled === 'function') { onFulfilled(this.value) } } if(this.status === Rejected){ if (onRejected && typeof onRejected === 'function') { onRejected(this.reason) } } } }Copy the code

Let’s test this with a test case:

Const test = new Promise((resolve,reject) =>{resolve(' 'successfully executed ')}) test.then(res =>{console.log(res)})Copy the code

You can print “Executed successfully” on the console, but only the promise for synchronization is handled here. What if I pass an asynchronous operation in the Executor function?

Const test = new Promise((resolve,reject) =>{setTimeout(() =>{resolve(' successful ')},1000)}) test.then(res =>{ console.log(res) })Copy the code

After 1 second, you will find that the console does not print “Successful execution” because when the THEN instance method is called, the Promise is in the Pending state and even though the Promise is in the Fulfilled state after 1 second, the THEN instance method has already been called.

How do you control the timing of the callback function in the THEN instance method? This can be implemented using the publisher-subscriber design pattern.

When the then instance method is called, if the Promise’s state is Pending, the success and failure callbacks are stored separately, and the execution of the asynchronous task ends in the Executor function, triggering the built-in resolve or reject methods. Where these callbacks are called in turn.

With that in mind, change the code again.

Const Pending = Symbol('Pending'); const Pending = Symbol('Pending'); This will be a pity. // this will be a pity. Const Rejected = Symbol('Rejected'); Class Promise {constructor(executor) {this.status = Pending; // Store the Promise state this.value = undefined; // Store the result of successful execution of the business code in the executor function this.reason = undefined; This. OnFulfilled = []; // This. This.onrejected = []; // This. OnRejected = []; Const resolve = value => {// Resolve = value => {// Resolve only if the state is Pending. if (this.status === Pending) { this.status = Fulfilled; this.value = value; This.onfulfilled. ForEach (fn=>fn()); // This.onfulfilled. }}; Const reject = value => {// Reject only when the state is Pending, ensuring that once the state changes it will not change again. if (this.status === Pending) { this.status = Rejected; this.reason = value; This.onrejected. ForEach (fn=>fn()); }}; executor(resolve, reject); } then(onFulfilled, onRejected) { if(this.status === Fulfilled){ if (onFulfilled && typeof onFulfilled === 'function') { onFulfilled(this.value) } } if(this.status === Rejected){ if (onRejected && typeof onRejected === 'function') { onRejected(this.reason) } } if(this.status === Pending){ if (onFulfilled && typeof onFulfilled === 'function') { this.onFulfilled.push(() =>{ onFulfilled(this.value) }) } if (onRejected && typeof onRejected === 'function') { this.onRejected.push(() =>{ onRejected(this.reason) }) } } } }Copy the code

After testing with the above test case, the console prints “Successful execution” after 1 second. Code logic is correct.

The business purpose of the THEN instance method should be to add a callback when the Promise state changes, passing the first argument to a successful callback and passing the second argument to a failed callback.

Implementation of instance method THEN microtask

Since the native Promise is a microtask provided by the V8 engine, we cannot restore the V8 implementation, so we use setTimeout here to simulate async, so native is a microtask, here is a macro task.

In addition, Promise A+ specification 3.1 also mentioned:

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

translation

This can be accomplished through a “macrotask” mechanism such as setTimeout or setImmediate, or a “microtask” mechanism such as MutatonObserver or process.nexttick.

If you want to implement promise microtasks, you can use mutationObserver instead of seiTimeout to implement microtasks. This is just simulating asynchrony.

then(onFulfilled, onRejected) {
    if (this.status === Fulfilled) {
        if (onFulfilled && typeof onFulfilled === 'function') {
            setTimeout(() => {
                onFulfilled(this.value)
            }, 0)
        }
    }
    if (this.status === Rejected) {
        if (onRejected && typeof onRejected === 'function') {
            setTimeout(() => {
                onRejected(this.reason)
            }, 0)
        }
    }
    if (this.status === Pending) {
        if (onFulfilled && typeof onFulfilled === 'function') {
            this.onFulfilled.push(() => {
                setTimeout(() => {
                    onFulfilled(this.value)
                }, 0)
            })
        }
        if (onRejected && typeof onRejected === 'function') {
            this.onRejected.push(() => {
                setTimeout(() => {
                    onRejected(this.reason)
                }, 0)
            })
        }
    }
}
Copy the code

Implementation of instance method THEN chained call

Instance method THEN chained calls have two requirements:

  • You can use the instance method THEN directly after the instance method THEN.

  • The first instance method then returns a value, and whatever value is retrieved in the next instance method THEN.

The implementation of chained calls is simple: return a new Promise object from the instance method then, and pass the value returned by the instance method then through either resolve(value) or reject(value).

The difficulty in implementing this feature is determining the type of the value returned by the instance method then and the corresponding processing. So write a utility function, handleValue, specifically to handle the value returned by the instance method then.

then(onFulfilled, onRejected) { let promise = new Promise((resolve, reject) => { if (this.status === Fulfilled) { if (onFulfilled && typeof onFulfilled === 'function') { setTimeout(() => {  let x = onFulfilled(this.value); handleValue(promise,x,resolve,reject); }, 0) } } if (this.status === Rejected) { if (onRejected && typeof onRejected === 'function') { setTimeout(() => { let x = onRejected(this.reason); handleValue(promise,x,resolve,reject); }, 0) } } if (this.status === Pending) { if (onFulfilled && typeof onFulfilled === 'function') { this.onFulfilled.push(() => { setTimeout(() => { let x = onFulfilled(this.value); handleValue(promise,x,resolve,reject); }, 0) }) } if (onRejected && typeof onRejected === 'function') { this.onRejected.push(() => { setTimeout(() => { let x = onRejected(this.reason); handleValue(promise,x,resolve,reject); }, 0) }) } } }) return promise }Copy the code

As mentioned above, the modified instance method THEN is now available for chained calls. But the previous instance method then has not yet been implemented and returns a value that can be retrieved in the later instance method THEN. This is then implemented in the handleValue function.

Implement the handleValue function as the Promise/A+ specification does for different types of return X, and watch for the code comments.

Const handleValue = (promise, x, resolve, reject) => { If (promise === x) {return reject(new TypeError(' chained loop reference detected for promise '))} // Ensure that the value is passed only once. Let once = false; if ((x ! = = = = = null && typeof x 'object') | | typeof x = = = 'function') {/ / prevent repeated to read x.t hen let then = x.t hen; If (typeof then === 'function') {if (typeof then === 'function') { once = true; // To prevent a Promise from being passed after a Promise has been successfully executed, recursive parsing is required. handleValue(promise, y, resolve, reject); }, r => { if (once) return; once = true; reject(r); } else {// if x is a normal object, call resolve(x) resolve(x); } else {// if x is a primitive value, call resolve(x) resolve(x); }}Copy the code

Typeof then === ‘function’ is a Promise. If there is no then function, x is an ordinary value and resolve(x) is returned. If there is a then function, x is a Promise and the Promise is recursively resolved until x is a normal value and returned as the final result.

Use typeof then === ‘function’ to determine whether x is a Promise, instead of using x instanceof Promise. This is to make promises more generic, so a thenable object can also be considered a Promise. The thenable object is an object that has a then method, as shown in the following code:

Let thenable = {then: function(reject, reject){resolve(' reject successfully ')}}Copy the code

The result of successful execution is passed through resolve in the thenable.then method. However, the thenable object is not created by the Promise class new, so it cannot be determined by x instanceof Promise.

Also mentioned in Promise A+ specification 1.1: A Promise is an object or function with A THEN method that behaves in accordance with this specification.

“Promise” is an object or function with a then method whose behavior conforms to this specification.

Implementation of instance method THEN value penetration

In the implementation of the method then chained call above, value passing is already implemented, of course, in the scenario where the THEN has parameters passed in.

Then no arguments are passed in the instance method THEN, for example:

Const test = new Promise((resolve, reject) => {setTimeout(() => {resolve('" successfully executed ")}, 3000) }) test.then().then(res =>{ console.log(res) })Copy the code

The later instance method then can still get the value returned by the previous instance method THEN. This is called value penetration. As in the example above, the console can still print “Successful execution”.

This can be done simply by modifying the instance method then.

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };;
    let promise = new Promise((resolve, reject) => {
        if (this.status === Fulfilled) {
            setTimeout(() => {
                let x = onFulfilled(this.value);
                handleValue(promise, x, resolve, reject);
            }, 0)
        }
        if (this.status === Rejected) {
            if (onRejected && typeof onRejected === 'function') {
                setTimeout(() => {
                    let x = onRejected(this.reason);
                    handleValue(promise, x, resolve, reject);
                }, 0)
            }
        }
        if (this.status === Pending) {
            this.onFulfilled.push(() => {
                setTimeout(() => {
                    let x = onFulfilled(this.value);
                    handleValue(promise, x, resolve, reject);
                }, 0)
            })
            if (onRejected && typeof onRejected === 'function') {
                this.onRejected.push(() => {
                    setTimeout(() => {
                        let x = onRejected(this.reason);
                        handleValue(promise, x, resolve, reject);
                    }, 0)
                })
            }
        }
    })
    return promise
}
Copy the code

7. Promise performs error handling internally

None of the above code catches a Promise internal execution error. You can use try… The catch statement catches the error and passes the error out to the built-in method reject, preventing the Promise from being untraceable internally.

Const Pending = Symbol('Pending'); const Pending = Symbol('Pending'); This will be a pity. // this will be a pity. Const Rejected = Symbol('Rejected'); // Fail const handleValue = (promise, x, resolve, reject) => { If (promise === x) {return reject(new TypeError(' chained loop reference detected for promise '))} // Ensure that the value is passed only once. Let once = false; if ((x ! = = = = = null && typeof x 'object') | | typeof x = = = 'function') {try {/ / prevent repeated to read x.t hen let then = x.t hen; If (typeof then === 'function') {if (typeof then === 'function') { once = true; // To prevent a Promise from being passed after a Promise has been successfully executed, recursive parsing is required. handleValue(promise, y, resolve, reject); }, r => { if (once) return; once = true; reject(r); } else {// if x is a normal object, call resolve(x) resolve(x); } } catch (err) { if (once) return; once = true; reject(err); } else {// if x is a primitive value, call resolve(x) resolve(x); } } class Promise { constructor(executor) { this.status = Pending; // Store the Promise state this.value = undefined; // Store the result of successful execution of the business code in the executor function this.reason = undefined; This. OnFulfilled = []; // This. This.onrejected = []; // This. OnRejected = []; Const resolve = value => {// Resolve = value => {// Resolve only if the state is Pending. if (this.status === Pending) { this.status = Fulfilled; this.value = value; This.onfulfilled. ForEach (fn => fn()); // This.onfulfilled. }}; Const reject = value => {// Reject only when the state is Pending, ensuring that once the state changes it will not change again. if (this.status === Pending) { this.status = Rejected; this.reason = value; This.onrejected. ForEach (fn => fn()); }}; try { executor(resolve, reject); } catch (error) { reject(error) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };; let promise = new Promise((resolve, reject) => { if (this.status === Fulfilled) { setTimeout(() => { try { let x = onFulfilled(this.value); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) } if (this.status === Rejected) { if (onRejected && typeof onRejected === 'function') { setTimeout(() => { try { let x = onRejected(this.reason); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) } } if (this.status === Pending) { this.onFulfilled.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) }) if (onRejected && typeof onRejected === 'function') { this.onRejected.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) }) } } }) return promise } }Copy the code

Viii. Test whether Promise meets the specification

There are special test scripts to test whether the code you write conforms to the PromiseA+ specification.

Install the test script:

npm install -g promises-aplus-tests
Copy the code
Copy the following code into promise.js
Const Pending = Symbol('Pending'); const Pending = Symbol('Pending'); This will be a pity. // this will be a pity. Const Rejected = Symbol('Rejected'); // Fail const handleValue = (promise, x, resolve, reject) => { If (promise === x) {return reject(new TypeError(' Chained loop reference detected for promise '))}} // Ensure that the value is passed only once in recursive parsing let once = false; if ((x ! = = = = = null && typeof x 'object') | | typeof x = = = 'function') {try {/ / prevent repeated to read x.t hen let then = x.t hen; If (typeof then === 'function') {if (typeof then === 'function') { once = true; // To prevent a Promise from being passed after a Promise has been successfully executed, recursive parsing is required. handleValue(promise, y, resolve, reject); }, r => { if (once) return; once = true; reject(r); } else {// if x is a normal object, call resolve(x) resolve(x); } } catch (err) { if (once) return; once = true; reject(err); } else {// if x is a primitive value, call resolve(x) resolve(x); } } class Promise { constructor(executor) { this.status = Pending; // Store the Promise state this.value = undefined; // Store the result of successful execution of the business code in the executor function this.reason = undefined; This. OnFulfilled = []; // This. This.onrejected = []; // This. OnRejected = []; Const resolve = value => {// Resolve = value => {// Resolve only if the state is Pending. if (this.status === Pending) { this.status = Fulfilled; this.value = value; This.onfulfilled. ForEach (fn => fn()); // This.onfulfilled. }}; Const reject = value => {// Reject only when the state is Pending, ensuring that once the state changes it will not change again. if (this.status === Pending) { this.status = Rejected; this.reason = value; This.onrejected. ForEach (fn => fn()); }}; try { executor(resolve, reject); } catch (error) { reject(error) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };; let promise = new Promise((resolve, reject) => { if (this.status === Fulfilled) { setTimeout(() => { try { let x = onFulfilled(this.value); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) } if (this.status === Rejected) { if (onRejected && typeof onRejected === 'function') { setTimeout(() => { try { let x = onRejected(this.reason); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) } } if (this.status === Pending) { this.onFulfilled.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) }) if (onRejected && typeof onRejected === 'function') { this.onRejected.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) }) } } }) return promise } static defer(){ let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } static deferred(){ let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } } module.exports = Promise;Copy the code

Then run the following command in the corresponding directory:

promises-aplus-tests promise.js
Copy the code

The following command output is displayed

All 872 test cases passed.

Implementation of the instance method catch

This. Catch is the alias of this.then(null, onRejected).

catch(onRejected){
	this.then(null, onRejected)
}
Copy the code

Implementation of static promise.resolve ()

Review the use of promise.resolve () first.

Promise.resolve() turns the passed parameter into a Promise object.

  • If the argument is a Promise instance, return the Promise instance.

  • If the argument is a thenable object,

    A thenable object refers to an object that has a THEN method, for example

    let thenable = { then(resolve,reject){ resolve (42); }}Copy the code

    The promise.resolve () method converts this object to a Promise object and immediately executes the thenable object THEN method.

  • If the argument is not an object with a THEN method or is not an object at all, the promise.resolve () method returns a new Promise instance with the status succeeded and passes the argument.

  • With no arguments, the promise.resolve () method allows a call with no arguments to return a new Promise instance with the successful state.

It’s easy to implement depending on its usage promise.resolve ()

class Promise{
    //...
    static resolve(param) {
        if (param instanceof Promise){
            return param;
        }
        return new Promise((resolve,reject) =>{
            if(
                param && 
                Object.prototype.toString.call(param) === '[object Object]' && 
                typeof param.then === 'function'
            ){
                setTimeout(() =>{
                    param.then(resolve,reject)
                },0)
            }else{
                resolve(param)
            }
        })
    }
}
Copy the code

Implementation of the static method promise.reject ()

Let’s review the use of promise.reject () : return a new Promise instance with a failed state and pass the argument as the reason for the failure.

It’s easy to implement promise.reject () depending on its usage

class Promise{
    //...
    static reject(param){
        return new Promise((resolve,reject) =>{
            reject(param)
        })
    }
}
Copy the code

Implementation of the static method promise.all ()

Let’s review the use of promise.all ().

Promise.all() wraps multiple Promise instances into a new one.

For example,p = promise.all (p1,p2,p3), where P1,p2, and p3 are not Promise instances, they are internally converted to Promise instances through promise.resolve (). The state of P is determined by P1, P2 and P3, which can be divided into two situations.

  • The return value of pl, p2, and p3 is passed as an array to the callback function of P.

  • As long as one of the states of PL p2 p3 becomes failed, the state of P becomes failed, and the return value of the first state of PL P2 p3 becomes failed

The callback function passed to p.

class Promise { //... Static all(promises) {// make promises into a real Array. Promises = Array. From (promises); return new Promise((resolve, reject) => { const length = promises.length; let value = []; if (length) { value = Array.apply(null, { length: length }) for (let i = 0; i < length; i++) { Promise.resolve(promises[i]).then( res => { value[i] = res; if (value.length == length) { resolve(value); } }, err => { reject(err) return; } ) } } else { resolve(value) } }) } }Copy the code

Implementation of the static method promise.race ()

Let’s review the use of promise.race ().

Promise.race() wraps multiple Promise instances into a new one.

For example,p = promise.all (p1,p2,p3), where P1,p2, and p3 are not Promise instances, they are internally converted to Promise instances through promise.resolve (). The state of P is determined by P1, p2, and p3. As soon as one of the states of PL, p2, and p3 changes, the state of P will immediately change accordingly, and the return value of the first state change in PL, p2, and p3 will be passed to the callback function of P.

class Promise { //... Static race(promises) {// make promises into a real Array. Promises = Array. From (promises); return new Promise((resolve, reject) => { const length = promises.length; if (length) { for (let i = 0; i < length; i++) { Promise.resolve(promises[i]).then( res => { resolve(res); return; }, err => { reject(err) return; } ) } } else { return } }) } }Copy the code

Xiv. Complete code

Const Pending = Symbol('Pending'); const Pending = Symbol('Pending'); This will be a pity. // this will be a pity. Const Rejected = Symbol('Rejected'); // Fail const handleValue = (promise, x, resolve, reject) => { If (promise === x) {return reject(new TypeError(' Chained loop reference detected for promise '))}} // Ensure that the value is passed only once in recursive parsing let once = false; if ((x ! = = = = = null && typeof x 'object') | | typeof x = = = 'function') {try {/ / prevent repeated to read x.t hen let then = x.t hen; If (typeof then === 'function') {if (typeof then === 'function') { once = true; // To prevent a Promise from being passed after a Promise has been successfully executed, recursive parsing is required. handleValue(promise, y, resolve, reject); }, r => { if (once) return; once = true; reject(r); } else {// if x is a normal object, call resolve(x) resolve(x); } } catch (err) { if (once) return; once = true; reject(err); } else {// if x is a primitive value, call resolve(x) resolve(x); } } class Promise { constructor(executor) { this.status = Pending; // Store the Promise state this.value = undefined; // Store the result of successful execution of the business code in the executor function this.reason = undefined; This. OnFulfilled = []; // This. This.onrejected = []; // This. OnRejected = []; Const resolve = value => {// Resolve = value => {// Resolve only if the state is Pending. if (this.status === Pending) { this.status = Fulfilled; this.value = value; This.onfulfilled. ForEach (fn => fn()); // This.onfulfilled. }}; Const reject = value => {// Reject only when the state is Pending, ensuring that once the state changes it will not change again. if (this.status === Pending) { this.status = Rejected; this.reason = value; This.onrejected. ForEach (fn => fn()); }}; try { executor(resolve, reject); } catch (error) { reject(error) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };; let promise = new Promise((resolve, reject) => { if (this.status === Fulfilled) { setTimeout(() => { try { let x = onFulfilled(this.value); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) } if (this.status === Rejected) { if (onRejected && typeof onRejected === 'function') { setTimeout(() => { try { let x = onRejected(this.reason); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) } } if (this.status === Pending) { this.onFulfilled.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) }) if (onRejected && typeof onRejected === 'function') { this.onRejected.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); handleValue(promise, x, resolve, reject); } catch (error) { reject(error) } }, 0) }) } } }) return promise } static resolve(param) { if (param instanceof Promise){ return param; } return new Promise((resolve,reject) =>{ if( param && Object.prototype.toString.call(param) === '[object Object]' && typeof param.then === 'function' ){ setTimeout(() =>{ param.then(resolve,reject) },0) }else{ resolve(param) } }) } static reject(param){ return new Promise((resolve,reject) =>{ reject(param) }) } static all(promises) { // Promises = Array. From (promises); // Promises = Array. return new Promise((resolve, reject) => { const length = promises.length; let value = []; if (length) { value = Array.apply(null, { length: length }) for (let i = 0; i < length; i++) { Promise.resolve(promises[i]).then( res => { value[i] = res; if (value.length == length) { resolve(value); } }, err => { reject(err) return; }} else {resolve(value)}}} static race(promises) {// promises = Array. From (promises); // promises = Array. return new Promise((resolve, reject) => { const length = promises.length; if (length) { for (let i = 0; i < length; i++) { Promise.resolve(promises[i]).then( res => { resolve(res); return; }, err => { reject(err) return; } ) } } else { return } }) } }Copy the code