concept

Promise is a class provided by ECMAScript 6 that aims to write complex asynchronous tasks more elegantly, addressing the difficulty of maintaining and controlling multiple asynchronous callbacks in JS.

When is it appropriate to use promises instead of traditional callback functions? Promises work well when multiple asynchronous operations need to be performed sequentially, for example, if you want to use an asynchronous method to first detect the username and then the password.

How to use Promise

A static method

methods instructions
Promise.resolve(param) New Promise(resolve.,reject){resolve(param)})
Promise.reject(reason) New Promise(resolve.,reject){reject(reason)})
Promise.all([p1,…,pn]) Input a group of promises to return a new promise, all promises are a big promise, the result is a big promise state; If one fails, the result is a promise
Promise.allSettled([p1,…,pn]) Input a set of promises and return a new promise. After all the promise states change, the result promise becomes a pity
Promise.race([p1,…,pn]) Entering a set of promises returns a new promise, and the promise state follows the promsie state of the first change. The first to return a promise is a success, the promise is a success, and the other is a failure

Instance methods

methods instructions
Promise. Then (onFulfilled onRejected) A callback after a promise state change that returns a new promise pair
Promise.catch(reason) Same as promise.then(null,onRejected), the promise state is rejected
Promise.finally( function(reason){} ) Will the promise be implemented regardless of its state

Application and analysis of Promise

light

Three seconds later a red light, two seconds later a green light, one second later a yellow light, and so on.

const LIGHTS = [
    {
        color: 'red'.time: 3000
    },
    {
        color: 'green'.time: 2000
    },
    {
        color: 'yellow'.time: 1000}];function light({color, time}) {
    return new Promise((resolve, reject) = > {
        try {
            setTimeout(() = > {
                console.log(new Date().getSeconds(), color);
                resolve();
            }, time);
        } catch(e) { reject(e); }}); }function main() {
    let p = Promise.resolve();
    LIGHTS.forEach(x= > {
        p = p.then(() = > light(x));
    });
    p.then(() = > main());
}
main();
Copy the code

Promise.all Concurrency limit

The number of concurrent promises to be executed at any given time is fixed, and the final execution result remains the same as the original promise.all.

function multiRequest(urls = [], maxNum) {
  // Total number of requests
  const len = urls.length;
  // Create an array based on the number of requests to hold the results of the requests
  const result = new Array(len).fill(false);
  // The current number of completions
  let count = 0;

  return new Promise((resolve, reject) = > {
    // Request maxNum
    while (count < maxNum) {
      next();
    }
    function next() {
      let current = count++;
      // Handle boundary conditions
      if (current >= len) {
        // When the request completes, set the promise to success and return result as a promise! result.includes(false) && resolve(result);
        return;
      }
      const url = urls[current];
      console.log(` began${current}`.new Date().toLocaleString());
      fetch(url)
        .then((res) = > {
          // Save the request result
          result[current] = res;
          console.log(Completed `${current}`.new Date().toLocaleString());
          // If the request is not complete, it is recursive
          if (current < len) {
            next();
          }
        })
        .catch((err) = > {
          console.log(End of `${current}`.new Date().toLocaleString());
          result[current] = err;
          // If the request is not complete, it is recursive
          if(current < len) { next(); }}); }}); }Copy the code

Promise execution order

/* The Promise constructor takes an executor, which is synchronous; Register the THEN function immediately after the construction, and execute the THEN function after the synchronization code is completed. * /
new Promise((resolve, reject) = > {
    console.log("1"); The Promise constructor takes a function that needs to be executed immediately, a synchronization task
    resolve();
  })
.then(() = > { // 2. Register the THEN method and add it to the microtask queue
    // 3. Start the microtask without synchronization code
    console.log("2");
    new Promise((resolve, reject) = > { // 4. Continue with the Promise constructor
        console.log("3");
        resolve();
    })
    .then(() = > { // 5. Register its THEN method and add it to the microtask queue
        console.log("4"); / / 7. Execution
    })
    .then(() = > { / / 8. Registration
        console.log("5"); / / 10. The execution
    });
})
.then(() = > { // 6. The first THEN is executed, and the second THEN of the outer Promise is registered
    console.log("6"); / / 9. Execution
});
// Output: 1 2 3 4 6 5
Copy the code
new Promise((resolve, reject) = > {
    console.log("1"); // 1. The constructor argument is executed first
    resolve();
  })
.then(() = > { // register the first then
    console.log("2"); // 3. Execute first then
    // If we see return, we need to complete the expression before we can execute the outer second THEN
    return new Promise((resolve, reject) = > {
        console.log("3"); // 4. Constructor execution
        resolve();
    })
    .then(() = > { / / 5. Registration
        console.log("4"); / / 6. Implementation
    })
    .then(() = > { / / 7. Registration
        console.log("5"); / / 8. Execution
    });
})
.then(() = > { / / 9. Registration
    console.log("6"); / / 10. The execution
});
// Output: 1 2 3 4 6 5
Copy the code

Write a Promise by hand

Promise A + specification

If we want to write A Promise by hand, we follow the Promise/A+ specification. The Promise/A+ specification details how to implement A standards-compliant Promise library.

  1. State of Promise

The three states of a Promise are pending, depressing and Rejected.

- Pending: indicates the initial state of the Promise. This state can be settled or fulfilled. This is a big pity: to fulfill (solve), which means that the execution will be successful. A state in which a Promise is resolved. The state cannot be changed and has a private value value. - Rejected: Indicates that the execution fails. A state in which Promise is rejected. The state cannot be changed and has a private reason, reason.Copy the code

Note: Value and Reason are also immutable, containing immutable references to the original value or object. The default value is undefined.

  1. Then method

A then method must be provided to access the current or final value or Reason. promise.then(onFulfilled, onRejected)

1. The then method takes two functions as arguments, and the arguments are optional. 2. If the optional parameter is not a function, it is ignored. 3. Both functions are executed asynchronously and are put into an event queue to wait for the next tick. 4. When the ondepressing function is called, the value of the current Promise will be passed in as a parameter. 5. When the onRejected function is called, the reason for the failure of the current Promise is passed as a parameter. 6. The then function returns a Promise. 7. Then can be called multiple times by the same Promise.Copy the code
  1. Promise resolution process

The resolution of a Promise is an abstract operation that accepts a Promise and a value x. The following cases are handled for different values of x:

1. X equals Promise raises TypeError and rejects Promise. 2. X is an instance of a Promise. If X is in a pending state, the Promise continues to wait until X fulfills or rejects it, otherwise it fulfills/rejects the Promise based on x's state. 3. X is an object or function that takes x. teng and calls it. This points to x when called. The result y obtained in the then callback function is passed into the new Promise solution, recursively called. If the execution fails, the Promise is rejected with the corresponding reason for the failure. This case is dealing with objects or functions that have then() functions, also called thenable. 4. Execute a Promise with x as a value if x is not an object or function.Copy the code

The complete code

Follow big guy article, can only say that understand and half memory of the code, pending further practice and deliberation…

Fortunately, the internal principle of promise is clearer after I comb through it. For details, see references 1 and 2 at the end of this article.

const FULFILLED = 'fulfilled';
const PENDING = 'pending';
const REJECTED = 'rejected';
class MyPromise {
    constructor(excutor) {
        // Executor accepts two arguments: resolve and reject, which let the user control whether a promise succeeds or fails. A call to resolve succeeds, while a call to reject fails.
        try {
            excutor(this.resolve, this.reject); // When a new Promise object is created, the execution function executes immediately, synchronously
        } catch (e) {
            this.reject(e);
        }
    }
    status = PENDING; // The status of the asynchronous task
    result = undefined; // Successful results
    reason = undefined; // Cause of failure
    onFulfilledCallBackArr = [];
    onRejectedCallBackArr = [];

    This is a window in resolve/reject. This is a window in resolve/reject
    // The resolve method will change the state to depressing
    resolve = (result) = > {
        if (this.status ! == PENDING)return; // The state can only be changed once
        this.status = FULFILLED;
        this.result = result;
        while (this.onFulfilledCallBackArr.length) {
            this.onFulfilledCallBackArr.shift()(result); }}The reject method changes the status to REJECTED
    reject = (reason) = > {
        if (this.status ! == PENDING)return; // The state can only be changed once
        this.status = REJECTED;
        this.reason = reason;
        while (this.onRejectedCallBackArr.length) {
            this.onRejectedCallBackArr.shift()(reason); }}// Then method is the core:
    // 1. Store callback functions, which support multiple calls to THEN. After asynchronous tasks are completed, successful/failed callback functions are executed in the registration sequence
    // 2. Return Promise and support chained calls
    // 3. The parameter is undefined
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason= > {
            throw reason
        };

        // Then chain-call, which returns a new promise
        const newPromise = new MyPromise((resolve, reject) = > {
            const fulfilledMicrotask = () = > {
                // Create a microtask and wait for newPromise to complete initialization
                queueMicrotask(() = > {
                    try {
                        // Get the result of the successful callback
                        const x = realOnFulfilled(this.result);
                        // Pass in resolvePromise
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch (error) {
                        reject(error)
                    }
                })
            };

            const rejectedMicrotask = () = > {
                // Create a microtask and wait for newPromise to complete initialization
                queueMicrotask(() = > {
                    try {
                        // Call the failed callback and return the reason
                        const x = realOnRejected(this.reason);
                        // Pass in resolvePromise
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch (error) {
                        reject(error)
                    }
                })
            };

            switch (this.status) {
                case PENDING: // Determine if the asynchronous task is not completed, save the successful and failed callbacks, and execute them successively after the task is completed
                    this.onFulfilledCallBackArr.push(fulfilledMicrotask);
                    this.onRejectedCallBackArr.push(rejectedMicrotask);
                    break;
                case FULFILLED:
                    fulfilledMicrotask();
                    break;
                case REJECTED:
                    rejectedMicrotask();
                    break; }});return newPromise;
    }

    catch (onRejected) {
        this.then(undefined, onRejected);
    }

    static resolve(x) {
        // If you pass MyPromise, return it directly
        if (parameter instanceof MyPromise) {
            return parameter;
        } else {
            return new MyPromise((resolve, reject) = >{ resolve(x) }); }}static reject(x) {
        return new MyPromise((resolve, reject) = > {
            reject(x);
        });
    }

    // All successful
    static all(promises) {
        return new MyPromise((resolve, reject) = > {
            let values = [];
            let count = 0;
            promises.map((p, index) = > {
                if (p instanceof MyPromise) {
                    p.then(v= > {
                        values[index] = v;
                    });
                } else {
                    values[index] = p;
                }
                count++;
                if(count === promises.length) { resolve(value); }})}); }// One success is a success
    static race() {
        return new MyPromise((resolve, reject) = > {
            promises.map(item= > {
                if (item instanceof Promise) {
                    item.then(
                        resolve, reject)
                } else {
                    resolve(item)
                }
            })
        });
    }

    // Internal method
    resolvePromise(newPromise, x, resolve, reject) {
        // If it is equal, return itself, throws a type error, and returns
        if (newPromise === x) {
            return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
        }
        if (x instanceof MyPromise) { // Determine if x is the MyPromise instance object
            // Execute x and call the then method, with the purpose of changing its state to pity or Rejected
            // x.then(value => resolve(value), reason => reject(reason))
            // After simplification
            x.then(resolve, reject);
        } else {
            / / common values
            resolve(x)
        }
    }
}

module.exports = MyPromise;
Copy the code

reference

  1. JavaScript advanced asynchronous programming
  2. Start with a Promise interview question that keeps me up at night and dive into the Promise implementation details
  3. “Write once” handwritten Promise whole family bucket +Generator+async/await