This article has participated in the “Newcomer Creation Ceremony” activity, and started the road of digging gold creation together

Handwriting implements Promise and its Api

In front end interviews, we often get these questions: “What do you know about the Promise APIS?”, “What are promise. all and promise. race?”, “Can you implement a promise. all (promise.race)

The author was also asked about handwritten promises.all during a recent summer internship interview

Therefore, here is a good summary of the Promise~

Promise’s past lives

The reason for Promise

First, we know that asynchronous requests are handled by callback functions in Node. Before promises, when we needed to nest multiple asynchronous requests, the code would look something like this:

const fs = require('fs'); fs.readFile('./user.txt', 'utf8', function (err, data) { fs.readFile(data, 'utf8', function (err, data) { fs.readFile(data, 'utf8', function (err, data) { console.log(data); })})})Copy the code

In order to get the result of the callback, we have to nest layer by layer, which is pretty gross. And basically we do a bunch of processing on each request, making the code harder to read and harder to maintain, the infamous callback hell

There are two reasons for callback hell:

1. Nested calls, where the output of the first function is often the input of the second function;

2. Multiple asynchronous requests are processed concurrently, and the final result of the request is often required to be synchronized during development.

Depending on what caused the callback hell, the direction of the problem is clear:

  1. Eliminate nesting: => chain calls for promises
  2. Result of a request to merge multiple tasks: => promise.all

If you use Promise to solve the above problem, it looks like this:

const fs = require('fs');
​
const read = (filename) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, 'utf8', (err, data) => {
            if (err) reject(err);
            resolve(data);
    })
}
                       
read('./user.txt').then(data => {
     return read(data);
}).then(data => {
     return read(data); 
}).then(data => {
    console.log(data);        
}).catch(err => {
    console.error(err);      
})
Copy the code

Clearly, the pattern of chained invocation through Promise greatly improves the readability and maintainability of the code. In other words, promises address the issue of asynchronous coding styles.

What is Promise?

The origin of promises mentioned above is to solve the problem of asynchronous coding style. So, what are promises and how do you use them?

describe

The Promise object is used to represent the final completion (or failure) of an asynchronous operation and its resulting value.

A Promise object represents a value that is not necessarily known when the Promise is created. It allows you to associate the eventual success return value or failure cause of an asynchronous operation with the appropriate handler. This allows asynchronous methods to return values as synchronous methods do: instead of returning the final value immediately, asynchronous methods return a promise to give the value to the consumer at some point in the future.

A Promise must be in one of the following states:

  • Pending: Initial state, neither honored nor rejected.
  • This is a big pity.: indicates that the operation completed successfully.
  • I have rejected the offer.: Indicates that the operation fails.

This is a big pity. The Promise object in the pending state will either be fulfilled through a value, or will be rejected through a reason (mistake). When one of these conditions occurs, our associated handlers, arrayed with the promise’s then methods, are called. If a promise has already been fulfilled or rejected by the time a corresponding handler is bound, the handler is invoked, so there is no state of contention between completing the asynchronous operation and the binding handler.

Because the promise.prototype. then and promise.prototype. catch methods return promises, they can be called chained.

For more information about promises, refer to the MDN documentation: Promises

To understand how Promises work and how to use them, try Using Promises

The Promise of the API

A static method

  • Promise.all(iterable)

    This method returns a new promise that will succeed only if all of the iterable promise objects succeed, or if any of the iterable promise objects fail. The new Promise object, after firing the success state, returns the success callback with an array of all the promise returns from iterable, in the same order as the iterable. If the new Promise triggers a failed state, it treats the error message from the first promise in iterable that triggers a failed state as its failed error message. The promise.all method is often used to process sets of states for multiple Promise objects. (See jquery.when method)

  • Promise.allSettled(iterable)

    This is a big pity. Wait until all promises are settled. Each promise is fulfilled or rejected. Return a promise that completes after all promises have completed. With an array of objects, each corresponding to the result of each promise.

  • Promise.any(iterable)

    Receives a collection of Promise objects, and when one Promise succeeds, returns the value of that successful Promise.

  • Promise.race(iterable)

    When any child promise in the Iterable argument succeeds or fails, the parent promise immediately calls the parent promise’s binding handle using the child promise’s success return value or failure details as arguments and returns the promise object.

  • Promise.reject(reason)

    Return a Promise object with a failed state and pass the given failure information to the corresponding handler

  • Promise.resolve(value)

    Returns a Promise object whose state is determined by the given value. If the value is thenable(that is, the object with the THEN method), the final state of the returned Promise object is determined by the then method execution; Otherwise (the value is empty, a basic type, or an object with no THEN method), the Promise object will be returned with the state of FULFILLED and the value will be passed to the corresponding THEN method. In general, if you don’t know if a value is a Promise object, use promise.resolve (value) to return a Promise object so that the value can be used as a Promise object.

    The use of the Promise

First, let’s review the basic use of promises

const p1 = new Promise((resolve, reject) => {
  console.log('create a promise');
  resolve('success')
})
​
console.log('after create a promise');
​
const p2 = p1.then(res => {
  console.log(res);
  throw new Error('failed')
})
​
const p3 = p2.then(res => {
  console.log('success:', res);
}, err => {
  console.log('failed:', err);
})
Copy the code

Output:

create a promise
after create a promise
success
failed: Error: failed
Copy the code
  • First, when we call a Promise, we return a Promise object.
  • When you build a Promise object, you pass in an Executor function, where the main business flow of a Promise is executed.
  • If the execution of the business running in the excutor function succeeds, the resolve function is called; If the execution fails, the reject function is called.
  • The state of the Promise is irreversible, and the result of the first call is taken by default when both resolve and reject are called.

The above briefly introduced some of the main use methods of Promise, combined with the Promise/A+ specification, we can analyze the basic characteristics of Promise:

  1. Promise has three states:pending.fulfilled, orrejected; “Specification Promise/A+ 2.1”
  2. new promise, you need to pass oneexecutor()The actuator, the actuator executes immediately;
  3. executorIt takes two arguments, respectivelyresolveandreject;
  4. The default state for promise ispending;
  5. There’s one promisevalueThe value of the saved status, which can beundefined/thenable/promise; “Specification Promise/A+ 1.3”
  6. There’s one promisereasonSave the value of the failed state; “Specification Promise/A+ 1.5”
  7. Promise only frompendingtorejected, or frompendingtofulfilledOnce the state is confirmed, it will not change;
  8. There must be a promisethenThis is a big pity, and then receives two parameters, which are onFulfilled and onRejected, the promise successful callback. “Specification Promise/A+ 2.2”
  9. If the promise has been successful by the time then is called, it is executedonFulfilled, the parameter ispromisethevalue;
  10. If the promise has failed by the time then is called, it is executedonRejected, the parameter ispromisethereason;
  11. If an exception is thrown in then, it is passed as an argument to the failed callback of the next THENonRejected;

Fulfill a Promise

Implement A Promise that conforms to the Promise A+ specification

PromiseA + specification

// Define the three states of Promise
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

const resolvePromise = (promise2, x, resolve, reject) = > {
   // Waiting for yourself to finish is A bad implementation, end with A type error
  if (promise2 === x) { 
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}// Promise/A+ 2.3.3.3.3 can only be called once
  let called;
  // Subsequent conditions must be strictly judged to ensure that the code can be used with other libraries
  if ((typeof x === 'object'&& x ! =null) | |typeof x === 'function') { 
    try {
      Reject (reject and resolve) Promise/A+ 2.3.3.1
      let then = x.then;
      if (typeof then === 'function') { 
        DefineProperty Promise/A+ 2.3.3.3
        then.call(x, y= > { 
          if (called) return;
          called = true;
          // Recursive resolution process (because there may still be promises in promises) Promise/A+ 2.3.3.3.1
          resolvePromise(promise2, y, resolve, reject); 
        }, err= > {
          // As long as I fail, I fail
          if (called) return;
          called = true;
          reject(err);
        });
      } else {
         // If x. Chen is A normal value, return resolve as A result. Promise/A+ 2.3.3.4resolve(x); }}catch (e) {
      / / Promise/A + 2.3.3.2
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // If x is A normal value, resolve is returned as the result. Promise/A+ 2.3.4
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    // State of the Promise
    this.status = PENDING;
    // Store the value of success status, default is undefined
    this.value = undefined;
    // Store the value of failed state, default is undefined
    this.reason = undefined;
    // Store the successful callback function
    this.onResolvedCallbacks = [];
    // Store the failed callback function
    this.onRejectedCallbacks = [];

    // Call this method on success
    const resolve = value= > {
      // If the argument is a promise, wait until the promise is resolved
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }

      // The state can only be updated PENDING, which prevents executor from calling resovle/ Reject twice
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // Execute the functions in the callback array in turn
        this.onResolvedCallbacks.forEach(fn= >fn()); }}// Call this method on failure
    const reject = reason= > {
      // The state can only be updated PENDING, which prevents executor from calling resovle/ Reject twice
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // Execute the functions in the callback array in turn
        this.onRejectedCallbacks.forEach(fn= >fn()); }}try {
      // Pass resolve and reject
      executor(resolve, reject);
    } catch (error) {
      // Reject exception directlyreject(error); }}then(onFulfilled, onRejected) {
    // onFufilled, onRejected does not send a value
    // A+ 2.2.1 /A+ 2.2.5 /Promise/A+ 2.2.7.3 /Promise/A+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
    // The error value is to be accessed later, so an error must be run here, otherwise it will be caught later in resolve
    onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };
     // Each call to THEN returns A new Promise promise /A+ 2.2.7 -- chain call
    const promise2 = new Promise((resolve, reject) = > {
      if (this.status === FULFILLED) {
        / / Promise/A + 2.2.2
        / / Promise/A + 2.2.4 - setTimeout
        setTimeout(() = > {
          try {
            const x = onFulfilled(this.value);
            // x may be a proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      }

      if (this.status === REJECTED) {
        setTimeout(() = > {
          try {
            const x = onRejected(this.reason);
            // x may be a proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      }

      if (this.status === PENDING) {
        // If the promise state is pending, the onFulfilled and onRejected functions need to be stored. After the state is determined, the corresponding functions will be executed successively
        this.onResolvedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0);
        });
        
        // If the promise state is pending, the onFulfilled and onRejected functions need to be stored. After the state is determined, the corresponding functions will be executed successively
        this.onRejectedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0); })}})return promise2;
  }

  catch(errorCallback) {
    return this.then(null, errorCallback);
  }

  finally(callback) { 
    return this.then(value= > {
      return Promise.resolve(callback()).then(() = > value);
    }, reason= > {
      return Promise.resolve(callback()).then(() = > {
        throwreason; })})}// Generates a successful promise by default.
  static resolve(data) {
    return new Promise((resolve, reject) = >{ resolve(data); })}static reject(reason) {
    return new Promise((resolve, reject) = >{ reject(reason); })}static all(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) = > {
      const resArr = [];
      let resolvedCount = 0;
      const processResultByKey = (value, index) = > {
        resArr[index] = value;
        if (++resolvedCount === promises.length) resolve(resArr);
      }

      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(res= > {
            processResultByKey(res, i);
          }, reject);
        } else{ processResultByKey(promise, i); }}})}static race(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) = > {
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(resolve, reject);
        } else{ resolve(promise); }}})}static allSettled(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) = > {
      const result = new Array(promises.length);
      let count = 0;
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(res= > {
            result[i] = { status: 'fulfilled'.value: res };
            count++;
            if (count === promises.length) resolve(result);
          }).catch(err= > {
            result[i] = { status: 'rejected'.reason: err };
            count++;
            if(count === promises.length) resolve(result); })}else {
          result[i] = { status: 'fulfilled'.value: promise };
          count++;
          if(count === promises.length) resolve(result); }}})}// Return the first successful result.
  static any(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) = > {
      const rejectedArr = [];
      let count = 0;
      if (promises === null || promises.length === 0) {
        reject('Invalid any');
      }
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(res= > {
            resolve(res);
          }, err= > {
            rejectedArr[i] = err;
            count++;
            if(count === promises.length) { reject(rejectedArr); }})}else {
          // The normal value was successfully processedresolve(promise); }})}}// Test whether the Promise complies with the specification
// npm install -g promises-aplus-tests
// promises-aplus-tests promise.js
Promise.defer = Promise.deferred = function () {
  let dtd = {};
  dtd.promise = new Promise((resolve, reject) = > {
    dtd.resolve = resolve;
    dtd.reject = reject;
  })
  return dtd;
}

Copy the code

Promise Series – Read this blogger’s series for systematic recommendations

Refer to the link

Interviewer: “Can you make a Promise by hand?”