In JS, if we want to conduct asynchronous operations gracefully, we must be familiar with a very important concept in ES6, Promise, which has a chain call, very good relief of the callback hell brought by traditional callback writing, is also the basis for understanding generator, async/await. In recent years, it is almost inevitable to investigate it in the interview of the early end. The main purpose of this paper is to record my process and thinking in realizing a Promise.

I have uploaded all the code to Github

Let’s start with the simplest example

This is the simplest use of a Promise. The code creates a Promise object, passes in an executor execution function, and at some point it executes its arguments reslove and reject in sequence, The arguments to resolve and Reject are then taken as arguments to the Promise object THEN. With this in mind, we can summarize:

  • We’ll need two variables to store the resolve and reject arguments, respectively
  • Because resolve and Reject are not processed together, we need a state variable to record a state of the Promise object
  • The then method requires passing in two methods that are called when the Promise object changes state

Now let’s do a simple implementation

  • The constructor

  • Then method

  • test

Now it looks like the simple code from the previous example can be executed in this function written by hand, but now there is a problem. All the functions we have written are synchronous logic. So how do we solve the asynchronous problem

Resolve is placed on the execution queue, and then logic is synchronized. This. Status is still pending when executing then. We can add pending logic to the then method, store the incoming function in an array, and execute it in a resolve or reject method

Join asynchronous operations

  • The constructor

  • then

  • test

Hee hee no accident, perfect the answer to the output of the expected, the next is the most important value chain calls and through the implementation of the chain calls can be said to be the most difficult to understand the whole implementation process, but in fact if you often said that difficult to remove recursive thoughts and JS based solid case is also very simple, If you don’t understand at the beginning, it doesn’t matter, the chicken will explain the whole process of running a code at the same time, I believe that you will understand the smart read twice!

Analyze first:

Then is a Promise object, right? That’s easy. In order to do that, we need to make sure that when we call then, we return a new Promise object. And then once we’re done with the chain we need to deal with the transfer, according to the Promise rules

  • If the return value x for THEN is an ordinary value, the result is passed as an argument to the successful callback for the next THEN;
  • If an exception is thrown in then, it is passed as an argument to the failed callback of the next THEN;
  • If the return value x of then is a promise, then the promise will be executed. If the promise is successful, the next THEN will be completed. If the failure, then the next failure; If an exception is thrown, the next then fails;
  • If the return value x for THEN and promise are the same reference object, creating a circular reference, then an exception is thrown and passed to the failed callback for the next THEN;
  • If the return value x of then is a promise, and x calls both resolve and reject, the first call takes precedence and all other calls are ignored.

The first part of the then is a Promise, and the resolve or reject methods depend on the return part. All we need to do is return a Promise instance each time we use the THEN, and then process the return part. Then walk

Chain calls

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

Copy the code
  • The constructor

  • Then highlight

  • GetResolveValue (method for handling the return part)

  • The test part

Lavie hello JS Hello TSCopy the code

Understand the process

Combined with the interpretation of the front and then after finish see this code could have a lot of friend or a kind of feeling, ok then we will according to the given earlier test code (in order to thoroughly understand I deliberately written test code is around, you don’t hit me, multiple recursive warning ⚠) walk again believe you to look at the chicken dishes write the process will feel, And Promsie is actually no more than that, and again this is going to be a little tricky but it’s important that you sit through it and understand what these functions do

New PromiseAll ((resolve, _) = > {setTimeout (() = > {resolve (' lavie little teacher younger brother ')}, 0)}) / / p1, then ((data) = > {the console. The log (data) return new PromiseAll((resolve,_)=>{ resolve(new PromiseAll((resolve,_)=>{ resolve('hello JS') })) }) } ) //p2 .then(data=>{ console.log(data) return new PromiseAll((resolve,_)=>{ resolve('hello TS') }) } ) //p3 .then(data=>console.log(data)) //p4Copy the code
  1. This code creates a PromiseAll instance p1 and adds an asynchronous function to the queue with status Pending
  2. The first PromsieAll instance P1 calls the then method to create a new instance P2. Since P1 is in a Pending state, the function passed in is placed in P1’s onSuccessCallback array until IT is called when P1’s solve executes
  3. P2 calls then to create P3, and the same p3 function is stored in P2’s onSuccessCallback array, while P3 calls then to create P4…

The above procedure is a synchronous event executed in the execution stack, so it is executed first

  1. Execute p1 to resolve(‘lavie ‘) and then change the status of p1 to Success and value to Lavie (‘lavie ‘)
Value = > {try {let result = onFulfilled (value) / / at this output lavie little teacher younger brother getResolveValue (promiseNext, result, resolve, reject) // promsieNext is p2} catch (error) {reject(error)}}Copy the code

Based on the function that we passed in, result here is going to be equal to this nested object called PC1

//PC1
new PromiseAll((resolve,_)=>{
            resolve(new PromiseAll((resolve,_)=>{
                resolve('hello JS')
            }))
        })
Copy the code

Good clear promsieNext and result after we will go to perform getResolveValue (promiseNext, result, resolve, reject) function in logic

First then = PC1. Then bind this to PC1 using the call method

So the first recursive this.value is

new PromiseAll((resolve,_)=>{
                resolve('hello JS')
            }) 
Copy the code

Then continue to call getResolveValue (promiseNext, next, and the resolve, reject) for next is equal to this. The value, so at this time next parameters into a PC2

//PC2
new PromiseAll((resolve,_)=>{
                resolve('hello JS')
            }) 
Copy the code

At this point, the second recursion this.value clearly becomes Hello JS PC3

Since PC3 is no longer an object, call resolve(‘hello JS’).

The resolve method was passed when P2 was created, so p2’s status changed from Pending to Success. This. value also becomes Hello JS, and then executes the method passed to P2’s onSuccessCallback array when p3 was created. This outputs Hello JS and continues p3’s recursion….. And the front of similar I will not continue to say,, this recursion ah speak is so around, but as long as you step by step with words, or very simple 😹, in fact, this step to understand very uncomfortable, but it does not matter to insist on their own to say several times so you will understand!

through

That’s pretty easy

similar

new PromiseAll((resolve,_)=>{
                resolve('hello JS')
            }).then.().then().then(data=>console.log(data))
Copy the code

This is actually done in the then method above

 onFulfilled = typeof onFulfilled === 'function'  ? onFulfilled : data => data
 onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
Copy the code

If you don’t have a function coming in, you just pass it along

Actually, the whole Promsie has been completed by now, but I didn’t write it in full accordance with the Promise/A+ specification. Interested partners can improve 🐶 by themselves

Let’s briefly note the implementation of other Promise methods

Other methods

Promise.prototype.catch

PromiseAll. Prototype. Catch = function (err) {return this. Then (null, err)} new PromiseAll ((resolve, reject) = > {reject (' error ')  }).catch(data=>console.log(data))Copy the code

Promise.resolve && Promise.reject


PromiseAll.resolve = function(value){
    return new PromiseAll((resolve,_)=>{
        resolve(value)
    })
}
PromiseAll.reject= function(reason){
    return new PromiseAll((_,reject)=>{
        reject(reason)
    })
}

Copy the code

Promise.all

PromiseAll.all = function(arr){ if(! Array.isarray (arr)){throw new TypeError(' pass an Array ')} return new PromiseAll((resolve,reject)=>{try {let resultArr = []  const length = arr.length for(let i = 0; i<length; i++){ arr[i].then(data=>{ resultArr.push(data) if(resultArr.length === length){ resolve(resultArr) } },reject) } } catch  (error) { reject(error) } }) } const p1 = new PromiseAll((resolve,_)=>{ setTimeout(()=>{ resolve('cjzz') },1000) }) const p2 = new PromiseAll((resolve,_)=>{ resolve('cjz') }) PromiseAll.all([p1,p2]).then(data=>console.log(data))// ['cjz','cjzz']Copy the code

Promise.race

PromiseAll.race = function(arr){ if(! Array.isarray (arr)){throw new TypeError(' please pass an Array ')} return new PromiseAll((resolve,reject)=>{try {const length = arr.length for(let i = 0; i<length; i++){ arr[i].then(resolve,reject) } } catch (error) { reject(error) } }) } PromiseAll.race([p1,p2]).then(data=>console.log(data,'race'))Copy the code

If you have any questions, please leave a message!