state

To understand a Promise, you need to know its three states

  • Pending
  • Resolved or very successful
  • Rejected (rejected)

“Pending” can be changed to “Resolved” or “Rejected”, but “Resolved” cannot be changed to “rejected” and “rejected” cannot be changed to “resolved”. In other words, a state, once successful, will not fail again, once failed, will not succeed again. New Promise instances default to pending.

then

Each Promise instance has a THEN method, which takes two additional parameters, a successful callback and a failed callback. The same instance can have multiple THEN methods, and all then successful methods are called on success and all THEN failed methods are called on failure.

In addition, the then method is asynchronous and has a more advanced name called microtasks, which correspond to macro tasks and are related to event loops, which are not described here.

Simple implementation

With these two concepts in mind, you can basically implement a crude version of Promise, with the following code

    class Promise {
        constructor(executor) {
            this.value = undefined;
            this.reason = undefined;
            this.status = 'pending';
            let resolve = value= > {
                this.value = value;
                this.status = 'resolved';
            }
            let reject = reason= > {
                this.reason = reason;
                this.status = 'rejected';
            }
            executor(resolve, reject);
        }
        then(onFulfilled, onRejected) {
            if (this.status === 'resolved') {
                onFulfilled(this.value);
            }
            if (this.status === 'rejected') {
                onRejected(this.reason); }}}Copy the code

error

When synchronizing to the Executor, an internal error may be reported, and when an internal error is reported, the Promise state is Rejected, and the incoming reject method is executed. This can be caught using a try catch, then the code is:

    try{
        executor(resolve, reject);
    }catch(e){
        reject(e);
    }
Copy the code

Asynchronous Status Modification

Changing Promise states in executors is often done asynchronously, for example

    let promise = new Promise((resolved, rejected) = > {
        setTimeout((a)= >{
            resolved('ok');
        },1000);
    })

    promise.then((data) = >{
        console.log(data);
    },(err)=>{
        console.log(err);
    });
Copy the code

When this happens, in the case of the scheme we just described, then executes before the resolve method, so success after 1 second does not trigger the success callback in THEN. How do you handle this?

In fact, this can be handled by publishing subscriptions to events. When executing then, the state of the current Promise is determined, and if pending, successful and failed callbacks are stored. When asynchronous resolve or Reject is triggered, We can declare two arrays to store them:

    constructor(executor) {

        this.value = undefined;
        this.reason = undefined;
        this.status = 'pending';
        this.onResolvedCallbacks = []; // Save callback successfully
        this.onRejectedCallbacks = []; // Store failed callback. }Copy the code

Add a judge to THEN whose state is pending. When the state is pending, store the incoming success and failure callbacks into the declared array:

    then(onFulfilled, onRejected) {

        if (this.status === 'resolved') {
            onFulfilled(this.value);
        }
        if (this.status === 'rejected') {
            onRejected(this.reason);
        }
        if (this.status === 'pending') {
            this.onResolvedCallbacks.push((a)= > {
                onFulfilled(this.value);
            });
            this.onRejectedCallbacks.push((a)= > {
                onRejected(this.reason); }); }}Copy the code

In resolve and reject, we add sequential execution of the callback array:

    let resolve = value= > {
        if (this.status === 'pending') {
            this.value = value;
            this.status = 'resolved';
            this.onResolvedCallbacks.forEach(fn= >fn()); }}let reject = reason= > {
        if (this.status === 'pending') {
            this.reason = reason;
            this.status = 'rejected';
            this.onRejectedCallbacks.forEach(fn= >fn()); }}Copy the code

Chain calls

use

Promise handles callback hell, using the same chained calls to THEN to make the code look more maintainable. First, let’s look at how chain calls are used

In Node, the fs module is usually used to perform file reads. Fs provides synchronous reads and asynchronous reads. For asynchronous reads, we can Promise the chained reads:

    const fs = require('fs');

    function readFlie(url, encoding) {
        return new Promise((resolved, rejected) = > {
            fs.readFile(url, encoding, (err, data) => {
                if (err) {
                    rejected(err);
                } else{ resolved(data); }}); }) } readFlie('./a.txt'.'utf8').then((data) = > {
        return readFlie(data, 'utf8')
    }).then((data) = > {
        return data
    }).then((data) = > {
        console.log(data);
    });
Copy the code

If the then returns a normal value, that value will be passed as an argument to the success callback of the next THEN. If the THEN throws an error, then returns a promise. If the then returns a normal value, that value will be passed to the success callback of the next THEN. The subsequent then must write an error callback to catch the thrown error, or catch can be used to handle the same, all errors can only be caught once. In addition, if the successful and failed callbacks in then are not written, the value will pass through that THEN to the next THEN.

implementation

To implement the chain call, it mainly depends on whether the data result returned after the execution of A THEN is A new promise. According to the promise A+ specification, A promisE2 needs to be added to the THEN to return, and the relationship between the execution result of callback in the THEN and the promisE2 needs to be determined. As follows:

   then(onFulfilled, onRejected) {

        let promise2 = new Promise((resolve, reject) = > {
            if (this.status === 'resolved') {
                try {
                    let x = onFulfilled(this.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            }
            if (this.status === 'rejected') {
                try {
                    let x = onRejected(this.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            }
            if (this.status === 'pending') {
                this.onResolvedCallbacks.push((a)= > {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                });
                this.onRejectedCallbacks.push((a)= > {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) { reject(e) } }); }});return promise2;
    }
Copy the code

The resolvePromise is the function that determines the relationship between the callback execution of promise2 and the return x in then, as follows:

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        reject(new TypeError('x can not be promise2'))}if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    resolvePromise(promise2, y, resolve, reject) // Recurse until resolved to a normal value
                }, err => {
                    reject(err)
                });
            } else {
                resolve(x)
            }
        } catch(e) { reject(e); }}else{ resolve(x); }}Copy the code

In fact, I have basically implemented A scheme that conforms to the PROMISE A+ specification, and added several promise methods

catch

To catch uncaught errors in the THEN chain, the catch is usually placed at the end of the THEN chain or in the middle, but this is not recommended. The program will throw a warning, which is implemented as follows:

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

all

Pass in a bunch of promises and return a new promise. When all the promises succeed, the then method that executes the new promise returns an array of successes, or fails if one fails and executes the failed callback:

    Promise.all = function (promises) {
        return new Promise((resolve, reject) = > {
            let arr = [];
            let i = 0;
            function processData(index, data) {
                arr[index] = data;
                if(++i == promises.length) { resolve(arr); }}for (let i = 0; i < promises.length; i++) {
                promises[i].then(data= > { // Data is the result of successprocessData(i, data); }, reject); }})}Copy the code

race

A bunch of promises are passed in, and a new promise is returned, which results in the first promise to be fulfilled

    Promise.race = function (promises) {
        return new Promise((resolve, reject) = > {
            for (let i = 0; i < promises.length; i++) { promises[i].then(resolve, reject); }})}Copy the code

[making] : github.com/kingDuiDui/…

[Reference] : Promise A+ specification

[译 文] : kkking.site/Blog/javasc…