results

Results first: Passed the Promise A+ test

The execution logic is consistent with V8. The next problem has to do with V8PromiseThe output is consistent0, 1, 2, 3, 4, 5, 6.

MyPromise.resolve()
  .then(() = > {
  console.log(0);
  return MyPromise.resolve(4);
})
  .then((res) = > console.log(res))

MyPromise.resolve()
  .then(() = > console.log(1))
  .then(() = > console.log(2))
  .then(() = > console.log(3))
  .then(() = > console.log(5))
  .then(() = > console.log(6))
Copy the code

The principle of

Let’s talk about what I think of as the promise principle.

  • Subscription-based publishing.
    • Subscribe to:thenSubscribe.
      • then(onFulfilled, onRejected)
    • Release:Resolve, rejectRelease.
      • new Promise(resolve => resolve(1))
  • Asynchronous.
    • Microtasks
      • We can get throughqueueMicrotaskCreate a microtask
  • Chain call.
  • The status cannot be changed after confirmation.

implementation

MyPromise is implemented using es6’s class, private properties, arrow functions, and queueMicrotask. The logic refers to the V8 source implementation (read the source article in Resources below). The code is very simple, just look at it.

class MyPromise {
    status = Pending   // Initial Pending state
    reaction = []      // Then subscription queue
    value = undefined
    
    constructor(executor) {
        // callOnes ensures that resolve, reject is invalid after one of the functions is executed
        const [resolve, reject] = callOnes(this.#resolve, this.#reject)
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }

    #resolve = (v) = > {
          // Resolve is passed in multiple cases
        this.#resolvePromise(v)
    }
    
    // Boolean state check function
    #fulfil = (v) = > {
        if (this.status === Pending) {
            this.status = Fulfilled
            this.value = v
            this.#triggerPromiseReactions()
        }
    }
    
    // Rejected State check function
    #reject = (reason) = > {
        if (this.status === Pending) {
            this.status = Rejected
            this.value = reason
            this.#triggerPromiseReactions()
        }
    }
    
    // Handle multiple cases of result in resolve
    #resolvePromise = (result) = > {
        / / the result is this
        if (this === result) {
            return this.#reject(new TypeError('The promise and the return value are the same'))}// Direct assignment without object.
        // Note that function counts as object and null counts as not
        if(! isObject(result)) {return this.#fulfil(result);
        }
        let then;
        try {
            / / then
            then = result.then;
        } catch (error) {
            // Then throws an error
            return this.#reject(error);
        }
        
        // Common objects, non-thenable objects
        if (typeofthen ! = ='function') {
            return this.#fulfil(result)
        }
        
        // result is myPromise or thenable
        // EcMA stipulates that the next microtask is unified
        queueMicrotask(() = > {
            if (then === this.then) {
                // Result is the value passed in, which is an instance of MyPromise.
                
                // Verify the status and value of this with result.
                // Why then? Because result might be in a Pending state.
                result.then(this.#fulfil, this.#reject)
                // Note that the microtask executes then, then triggers the microtask, where there are two microtasks execution time.
            } else {
                // is thenable (that is, the object with the "then" method)
                const [resolve, reject] = callOnes(this.#resolve, this.#reject)
                try {
                    // Execute this to thenable
                    then.call(result, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            }
        })
    }
    
    // Subscribe ondepressing, onRejected
    then(onFulfilled, onRejected) {
        // Return a new promise
        const promise = new MyPromise(() = > {})
        // Save the subscription object subscribed to onFulfilled and onRejected
        this.reaction.push({
            onFulfilled,
            onRejected,
            promise, // Save the returned promise for the chained call
        })
        // Status confirmed, start publishing
        if (this.status ! == Pending) {this.#triggerPromiseReactions()
        }
        return promise
    }
    
    // Publish: Execute all subscriptions to THEN and confirm the state of the promise returned by THEN.
    #triggerPromiseReactions = () = > {
        const reaction = this.reaction
        this.reaction = []
        const handlerKeyMap = {
            [Fulfilled]: 'onFulfilled',
            [Rejected]: 'onRejected',}const handlerKey = handlerKeyMap[this.status]
        
         // Publish: Execute all subscriptions to THEN and confirm the state of the promise returned by THEN.
        for (let {[handlerKey]: handler, promise} of reaction) {
              // handler: fetch the corresponding subscription function according to status
            // The promise: then method creates and returns
              
           // microtasks that handle subscriptions to the THEN method
            queueMicrotask(() = > {
                if (typeofhandler ! = ='function') {
                    // then has no subscription function. eg: then(null,null)
                    // Verify the state and value of the promise.
                    switch (this.status) {
                        case Fulfilled:
                            return promise.#resolve(this.value)
                        case Rejected:
                            return promise.#reject(this.value)
                    }
                } else {
                    // then has subscription functions. eg: then(()=>1))
                    // Verify the state and value of the Promise by executing the subscription function
                    try {
                        let result = handler(this.value)
                        return promise.#resolve(result)
                    } catch (e) {
                        return promise.#reject(e)
                    }
                }
            })
        }
    }
    
    static resolve(v) {
        // ecMA specifies that promise. resolve passes a Promise and returns it directly.
        if (v instanceof MyPromise) { return v }
        return new MyPromise((resolve) = > resolve(v))
    }
    
    static reject(reason) {
        return new MyPromise((_, reject) = > reject(reason))
    }
}

const Pending = 'Pending'
const Fulfilled = 'Fulfilled'
const Rejected = 'Rejected'
// Only one can be executed
function callOnes(a, b) {
    let canCalled = true
    function warp(fn) {
        return (. args) = > {
            if(canCalled) { fn(... args) } canCalled =false}}return [warp(a), warp(b)]
}
/ / determine the object
const isObject = v= >v ! = =null && typeof v === 'object' || typeof v === 'function'
Copy the code

Why is there no other method like catch?

Because Promise A+ only says that there are then methods. And all the other methods wrap the THEN methods. Example: the catch

class MyPromise {
  catch(onReject) {
    return this.then(null, onReject)
  }
}
Copy the code

Promise A + test

Github, github.com/promises-ap… Configuration package. Json

{
  "name": "promise"."version": "1.0.0"."description": ""."main": "index.js"."devDependencies": {
    "promises-aplus-tests": "*"
  },
  "scripts": {
    "test": "promises-aplus-tests src/promise.js"
  },
  "keywords": []."author": ""."license": "ISC"
}
Copy the code

To begin testing

npm run test
Copy the code

Perfect through

Is the execution time the same as V8

I found a few questions to test. You can replace MyPromise with Promise, and the output is the same.

 MyPromise.resolve()
   .then(() = > {
   console.log(0);
   throw Promise.resolve(4);
 })
   .catch((res) = > console.log(res))
   .then(() = > console.log(7))

MyPromise.resolve()
  .then(() = > console.log(1))
  .then(() = > console.log(2))
  .then(() = > console.log(3))
  .then(() = > console.log(5))
  .then(() = > console.log(6))
// Output: 0 1 2 3 4 5 6
Copy the code
let p1 = MyPromise.resolve()
  .then(v= > console.log(1))
  .then(v= > console.log(2))
  .then(v= > console.log(3))

p1.then(v= > console.log(4))
p1.then(v= > console.log(5))

let p2 = MyPromise.resolve()
  .then(v= > console.log(11))
  .then(v= > console.log(22))
  .then(v= > console.log(33))

p2.then(v= > console.log(44))
p2.then(v= > console.log(55))
// Output: 1 11 2 22 3 33 4 5 44 55
Copy the code

conclusion

Our MyPromise does the following:

  • Passed the Promise A+ test.
  • Microtask execution timing is consistent with V8.

The code is available on Github at github.com/liu-zhi-fei… .

The resources

  • zhuanlan.zhihu.com/p/264944183
  • zhuanlan.zhihu.com/p/329201628
  • www.zhihu.com/question/45…
  • Chromium.googlesource.com/v8/v8.git/+…
  • Github.com/xianshenglu…
  • tc39.es/ecma262/
  • Github.com/promises-ap…