Writing in the front

Javascript asynchronous programming goes through four phases: Callback, Promise, Generator, and Async/Await. A Promise is not A new transaction, but A class implemented according to A specification. There are many such specifications, such as Promise/A, Promise/B, Promise/D and Promise/A’s upgrade, Promise/A+, and eventually the Promise/A+ specification was adopted in ES6. Later, Generator functions and Async functions are further encapsulated based on Promise, which shows the importance of Promise in asynchronous programming.

There was a lot of information about Promise, but everyone understood it differently, and different ideas yielded different results. This article will focus on the realization of Promise and some of my experiences in daily use.

Realize the Promise

Normative interpretation

Promise/A+ specification is mainly divided into terms, requirements and matters needing attention in three parts, we focus on the second part is the part of the requirements, to the author’s understanding roughly explain, specific details refer to the full version of Promise/A+ standard.

1, this is very depressing. (For consistency, this article calls the fulfilled state as the resolved state)

  • The state transition can only bependingtoresolvedorpendingtorejected;
  • Once the state has been converted, it cannot be converted again.

Promise has a THEN method that handles values in the Resolved or Rejected states.

  • thenMethod takes two parametersonFulfilledandonRejectedThe two argument variables are of function type, otherwise they are ignored, and both arguments are optional.
  • thenMethod must return a new onepromise, aspromise2“That’s guaranteedthenThe method can be the samepromiseThe previous call. (ps: The specification only requires returnpromiseIs not explicitly required to return a new onepromiseTo be consistent with the ES6 implementation, we also return a newpromise)
  • onResolved/onRejectedIf there is a return value, the return value is defined asx[[Resolve]](promise2, x);
  • onResolved/onRejectedRun error, thepromise2Set torejectedState;
  • onResolved/onRejectedIf it’s not a function, you need to putpromise1Is passed on.

3. Different promise implementations can interact.

  • The specification calls this steppromise[[Resolve]](promise, x),promiseFor the new to be returnedpromiseObject,xforonResolved/onRejectedThe return value of. ifxThere arethenMethod and it looks like apromiseLet’s just think of x as apromiseObject of, i.ethenableObject, in which case try to makepromisereceivexIn the state. ifxnotthenableObjectxValue ofpromise.
  • [[Resolve]](promise, x)
    • ifpromisexPoints to the same object asTypeErrorRefuse to execute on grounds of evidencepromise;
    • ifxPromise, then makepromiseacceptxThe state;
    • ifxFor object or function, takex.thenIf there is an error in the value, letpromiseEnter therejectedState, ifthenIt’s not a functionxnotthenableObject, directly toxThe value of theresolveIf thethenExists and is a function, then thexAs athenThe scope of a functionthisThe call,thenMethod takes two parameters,resolvePromiseandrejectPromiseIf theresolvePromiseIs executed, thenresolvePromiseThe parameters of thevalueAs axContinue calling [[Resolve]](promise, value) untilxNot an object or a function, ifrejectPromiseIs executed to letpromiseEnter therejectedState;
    • ifxNot an object or a function, just use itxValue ofpromise.

Code implementation

Article 1 of specification interpretation, code implementation:

class Promise {
  // Define a Promise state with an initial value of pending
  status = 'pending';
  Since the then method needs to handle the Promise's success or failure value, a global variable is needed to store this value
  data = ' ';

  // The Promise constructor is passed as an executable function
  constructor(executor) {
    // The resolve function is responsible for changing the state to resolved
    function resolve(value) {
      this.status = 'resolved';
      this.data = value;
    }
    // The reject function is responsible for converting the state to rejected
    function reject(reason) {
      this.status = 'rejected';
      this.data = reason;
    }

    // Execute the executor function directly, taking the resolve, reject functions. Because the executor process can make errors, error cases need to be rejected
    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e)
    }
  }
}
Copy the code

The first one is that the implementation is complete. It is relatively simple and easy to understand with code comments.

Specification interpretation article 2, code implementation:

  /** * return a new Promise */; /** * return a new Promise */; /** * return a new Promise */
  then(onResolved, onRejected) {
    // Set the default parameters for then, which pass through the Promise values
    onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return e };
    onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e };
    
    let promise2;
    
    promise2 =  new Promise((resolve, reject) = > {
      // If the state is Resolved, execute onResolved
      if (this.status === 'resolved') {
        try {
          // onResolved/onRejected
          const x = onResolved(this.data);
          // Execute [[Resolve]](promise2, x)
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) { reject(e); }}// If the state is rejected, then onRejected is executed
      if (this.status === 'rejected') {
        try {
          const x = onRejected(this.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) { reject(e); }}});return promise2;
  }
Copy the code

Now that we have implemented article 2 of the specification, the code above is clearly problematic, and the problem is as follows

  1. resolvePromiseUndefined;
  2. thenWhen the method executes,promiseMay still be inpendingState, becauseexecutorThere may be asynchronous operations (most of which are actually asynchronous) in theonResolved/onRejectedLost execution time;
  3. onResolved/onRejectedThese two functions need to be called asynchronously (officialPromiseImplemented callback functions are always invoked asynchronously).

The solution:

  1. Read article 3 according to the specification, define and implement itresolvePromiseFunctions;
  2. thenMethod is executed ifpromiseIs still in thependingState, the processing function is stored, etcresolve/rejectWhen the function is actually executing.
  3. promise.thenIt’s a microtask, so here we use macro tasks for conveniencesetTiemoutInstead of implementing asynchrony, the details are recommendedThis article.

Ok, with the solution in hand, let’s take the code one step further:

class Promise {
  // Define a Promise state variable with the initial value pending
  status = 'pending';
  // Since we need to handle the Promise's success or failure value in the THEN method, we need a global variable to store this value
  data = ' ';
  // The set of callbacks for Promise resolve
  onResolvedCallback = [];
  // Set of callback functions for Promise reject
  onRejectedCallback = [];

  // The Promise constructor is passed as an executable function
  constructor(executor) {
    // The resolve function is responsible for changing the state to resolved
    function resolve(value) {
      this.status = 'resolved';
      this.data = value;
      for (const func of this.onResolvedCallback) {
        func(this.data); }}// The reject function is responsible for converting the state to rejected
    function reject(reason) {
      this.status = 'rejected';
      this.data = reason;
      for (const func of this.onRejectedCallback) {
        func(this.data); }}// Execute the executor function directly, taking the resolve, reject functions. Because the executor process can make errors, error cases need to be rejected
    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e)
    }
  }
  /** * return a new Promise */; /** * return a new Promise */; /** * return a new Promise */
  then(onResolved, onRejected) {

    // Set the default parameters for then, which pass through the Promise values
    onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return v };
    onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e };

    let promise2;

    promise2 =  new Promise((resolve, reject) = > {
      // If the state is Resolved, execute onResolved
      if (this.status === 'resolved') {
        setTimeout(() = > {
          try {
            // onResolved/onRejected
            const x = onResolved(this.data);
            // Execute [[Resolve]](promise2, x)
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      }
      // If the state is rejected, then onRejected is executed
      if (this.status === 'rejected') {
        setTimeout(() = > {
          try {
            const x = onRejected(this.data);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      }
      // If the state is pending, the handler is stored
      if (this.status = 'pending') {
        this.onResolvedCallback.push(() = > {
          setTimeout(() = > {
            try {
              const x = onResolved(this.data);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0);
        });

        this.onRejectedCallback.push(() = > {
          setTimeout(() = > {
            try {
              const x = onRejected(this.data);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0); }); }});return promise2;
  }

  // [[Resolve]](promise2, x
  resolvePromise(promise2, x, resolve, reject){}}Copy the code

At this point, the then part of the specification is fully implemented. Detailed comments are added to the code, which are not hard to understand.

Article 3 of specification interpretation, code implementation:

// [[Resolve]](promise2, x
  resolvePromise(promise2, x, resolve, reject) {
    let called = false;

    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected for promise! '))}// If x is still a Promise
    if (x instanceof Promise) {
      // If the state of x has not been determined, then it is possible that the final state and value of x will be determined by a thenable, so continue calling resolvePromise
      if (x.status === 'pending') {
        x.then(function(value) {
          resolvePromise(promise2, value, resolve, reject)
        }, reject)
      } else { 
        // If the x state is already determined, take it directly
        x.then(resolve, reject)
      }
      return
    }
  
    if(x ! = =null && (Object.prototype.toString(x) === '[object Object]' || Object.prototype.toString(x) === '[object Function]')) {
      try {
        // Because x. hen may be a getter, in which case multiple reads may cause side effects, so the variable called is used to control
        const then = x.then 
        // if then is a function, then x is thenable, then continue to execute the resolvePromise function until x is normal
        if (typeof then === 'function') { 
          then.call(x, (y) = > { 
            if (called) return;
            called = true;
            this.resolvePromise(promise2, y, resolve, reject);
          }, (r) = > {
            if (called) return;
            called = true; reject(r); })}else { Resolve x resolve x resolve x resolve x resolve x
          if (called) return ;
          called = true; resolve(x); }}catch (e) {
        if (called) return;
        called = true; reject(e); }}else{ resolve(x); }}Copy the code

This step is as simple as translating it into code according to the specification.

Resolve, promise. reject, and promise. all are not specified in the specification. Let’s take a look at the common methods of promises.

Promise other method implementations

1. Catch method

The CATCH method is an encapsulation of the THEN method and is only used to receive error messages in reject(Reason). If you do not pass the onRejected parameter then, the error message will be passed until you have onRejected. If you do not pass the onRejected parameter then, the error message will be passed until you have onRejected. All you need to do is add a catch() at the end of the chain, so that any errors that happen to the promise in the chain are caught by the last catch.

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

A catch is called at the end of a promise call and is used to catch errors in the chain. However, errors can also occur inside a catch method, so some promise implementations add a done method that provides an error-free catch method. It does not return a promise, usually to end a promise chain.

  done() {
    this.catch(reason= > {
      console.log('done', reason);
      throw reason;
    });
  }
Copy the code
3. Finally method

The finally method is used with either resolve or reject, and finally arguments are executed.

  finally(fn) {
    return this.then(value= > {
      fn();
      return value;
    }, reason= > {
      fn();
      throw reason;
    });
  };
Copy the code
4, promise.all method

The promise. all method receives a Promise array, returns a new promise2, and executes all promises in the array. If all promises are in the Resolved state, promise2 is in the resolved state and returns all Promise results. The result is in the same order as the PROMISE array. If one promise is in the Rejected state, the whole Promise2 enters the rejected state.

  static all(promiseList) {
    return new Promise((resolve, reject) = > {
      const result = [];
      let i = 0;
      for (const p of promiseList) {
        p.then(value= > {
          result[i] = value;
          if(result.length === promiseList.length) { resolve(result); } }, reject); i++; }}); }Copy the code
5, promise.race method

The promise. race method receives a Promise array, returns a new promise2, and executes the promises in the array in order. If a Promise state is determined, the promise2 state is determined, and is consistent with the state of the Promise.

  static race(promiseList) {
    return new Promise((resolve, reject) = > {
      for (const p of promiseList) {
        p.then((value) = >{ resolve(value); }, reject); }}); }Copy the code
6, promise.resolve method/promise.reject

Promise.resolve generates a Promise (rejected), and promise. reject generates a Promise (rejected).

  static resolve(value) {
    let promise;

    promise = new Promise((resolve, reject) = > {
      this.resolvePromise(promise, value, resolve, reject);
    });
  
    return promise;
  }
  
  static reject(reason) {
    return new Promise((resolve, reject) = > {
      reject(reason);
    });
  }
Copy the code

There are many extension methods, which I won’t show you here. They are basically a further encapsulation of the THEN method. As long as your THEN method is fine, all other methods can rely on the THEN method.

Promise Interview

Interview related questions, the author only said the situation of our company in recent years, and can not represent the whole situation, reference can be. Promise is a required knowledge point for front-end development positions, NodeJS development positions and full stack development positions of our company. The main questions will be distributed in three aspects: Promise introduction, basic application methods and deep understanding. Generally, there are 3-5 questions, which will be increased or decreased appropriately according to the answers of the interviewees.

1. Brief introduction to Promise.

Promise is a solution to asynchronous programming that is more rational and powerful than the traditional solution of callbacks and events. It was first proposed and implemented by the community, and ES6 wrote it into the language standard, unifying usage and providing Promise objects natively. With Promise objects, you can express asynchronous operations as synchronized operations, avoiding layers of nested callbacks. In addition, Promise objects provide a uniform interface that makes it easier to control asynchronous operations. (Of course, you can also briefly introduce the promise state, what methods there are, what problems there are with the callback, etc., which is more open)

  • Question probability: 99%
  • Scoring standard: humanized judgment, this problem is generally as an introduction problem.
  • Bonus points: Be able to say exactly what problems the Promise solves, what disadvantages it has, where it will be applied, etc.
2. Implement a simple Promise class that supports asynchronous chained calls.

The answer is not fixed. See minimalist Implementation Promise for support for asynchronous chained calls

  • Probability of asking questions: 50% (hand-held code questions, because these questions are time-consuming, an interview does not come up with many, so they are not very frequent, but they are necessary)
  • Bonus points: basic function implementation on the basis ofonResolved/onRejectedFunction asynchronous call, reasonable error capture and other highlights.
3, the order in which promise. then is executed in the Event Loop. (You can either ask directly or give specific questions for the interviewer to answer in print order)

JS is divided into two types of tasks: macroTask and microtask, macroTask includes: main code block, setTimeout, setInterval, setImmediate, etc. (setImmediate stipulates: Triggered on the next Event Loop (macro task); Microtask include: Promise, process. NextTick, etc. If a microTask is encountered during execution, it is added to the task queue of the microtask. After the macroTask completes, it immediately executes all microTask tasks in the current microtask queue (in turn), and then starts the next MacroTask task (obtained from the event queue)

  • Probability: 75% (3 out of 4 interviewsJSUnderstanding the operating mechanism)
  • Bonus: The extension explains how the browser works.
4. Describe some static methods of Promise.

All, promise. race, promise. resolve, promise. reject, etc

  • Probability of asking a question: 25% (basic question, usually asked when the answer to other questions is not satisfactory, or to lead to the next question)
  • Bonus: The more the better
5. What are the disadvantages of Promise?

1. You cannot cancel a Promise. Once a Promise is created, it is executed immediately. If the callback function is not set, the Promise will throw an error that is not reflected externally. If you add a catch at the end of the Promise chain, there may still be an error that you can’t catch (and there may be an error inside of the catch). 4. Reading the code is not easy.

  • Question probability: 25% (this question is used as an enhancement question, the probability of occurrence is not high)
  • Bonus: The more, the more reasonable, the better.

(This topic, welcome to add the answer)

6. Use Promise for sequence processing.

Use async functions with await or generator functions with yield. 2. Use promise.then through the for loop or array.prototype.reduce.

function sequenceTasks(tasks) {
    function recordValue(results, value) {
        results.push(value);
        return results;
    }
    var pushValue = recordValue.bind(null[]);return tasks.reduce(function (promise, task) {
        return promise.then(() = > task).then(pushValue);
    }, Promise.resolve());
}
Copy the code
  • The question probability: 90% (our company asks the question with high probability, that is, can check the interviewer’s rightpromiseYou can look at the logic of the program, and finallybindandreduceEtc.)
  • Rating criteria: Name any solution, but only name oneasyncFunctions andgeneratorYou can get 20% of the score of the function, you can usepromise.thenCooperate withforYou can get 60% of the score if you do it in a circular wayArray.prototype.reduceThose who do get the final 20%.
How to stop a Promise chain?

Add a method at the end of the promise chain that returns a promise that never executes resolve or Reject. The promise is always pending, so it will never execute then or catch down. So we’ve stopped a promise chain.

    Promise.cancel = Promise.stop = function() {
      return new Promise(function(){})}Copy the code
  • Probability: 50% (this question mainly examines the interviewer’s thinking)

(This topic, welcome to add the answer)

8. What if the last Promise returned on the Promise chain is wrong?

A catch is called at the end of a promise call and is used to catch errors in the chain. However, errors can also occur inside a catch method, so some promise implementations add a done method that provides an error-free catch method. It does not return a promise, usually to end a promise chain.

  done() {
    this.catch(reason= > {
      console.log('done', reason);
      throw reason;
    });
  }
Copy the code
  • Question probability: 90% (also as a very high rate of a question, fully investigate the interviewer’s rightpromiseUnderstanding of)
  • Bonus: Be specificdone()Method code implementation
9. What are Promise use techniques or best practices?

1. Chained promise returns a promise, not just constructs one. 2, reasonable use promise. all and promise. race and other methods. You can only add a catch() to the end of the promise chain, so all the errors in the promise chain will be caught by the catch(). If the catch() code has the potential for an error, the done() function needs to be added at the end of the chained call.

  • Question probability: 10% (a question with very low question probability)
  • Bonus: The more the better

(This topic, welcome to add the answer)

So far, we have listed some interview questions about Promise, some questions are open, welcome to complement and improve. To sum up, Promise is relatively easy to master and pass as part of the js interview requirement.

conclusion

Promise as a necessary skill for all JS developers, its implementation idea is worth learning by all people, through this article, I hope that friends in the future coding process can more skilled, more understand the use of Promise.

Reference links:

Liubin.org/promises-bo… Github.com/xieranmaya/… Segmentfault.com/a/119000001…