The concept of Promise

Promise Basic concepts

Promise is a new solution for asynchronous programming in JS, a way to encapsulate asynchronous operations and get their results. In the past, the solution to asynchron was to use callback functions, but in some cases, too many asynchronous events formed a callback hell, which is not good for reading and aesthetics, so the Promise method was introduced to solve the callback hell.

Synchronous vs. Asynchronous (Understanding)

First of all, the operation of the JS is a single thread of operation mode, and the single thread running means that in some time-consuming tasks such as IO operations, such as file reading can appear process jams, process blocked page will appear caton even jammed, this is obviously not friendly, so in order to solve this problem is the existence of the asynchronous operation. All tasks can be divided into synchronous tasks and asynchronous tasks. Synchronous tasks are executed on the main thread in order (when the main thread executes, it forms an execution stack), and asynchronous tasks are placed on the message queue (where asynchronous tasks are queued for execution). When all synchronous tasks on the main thread are finished, the system will see which asynchronous tasks in the message queue have completed, and then the system will put the completed asynchronous tasks on the main thread execution stack for execution. After execution, the system will check the message queue again and repeat the EventLoop. Summary: JavaScript operation mechanism: as long as the main thread is empty, it will read the “message queue”, the message queue has task queue and microtask queue

Macro tasks versus microtasks

When the main thread executes, an execution stack is generated, and the code in the execution stack is called a macro task, and the asynchronous task executed before the next macro task is executed is a microtask, which can be understood as the callback event of some functions in the macro task. Microtasks are executed immediately after the current macro task is executed, but there is also a queue between microtasks. Macro tasks: I/O setTimeOut, setInterval, setImmediate, requestAnimationFrame Microtasks: Process. nextTick, MutationObserver, promise. then catch finally

Class methods, instance methods, prototype methods

class M {
    // Static methods can only be called by classes
    static a(){}
    // Instance methods can only be called instance
    a = () = > {}
    // The prototype method can be called
    a(){}}Copy the code

Learn promises by writing them

How to Use Promises

Example 1: Common use

let promise = new Promise((resolve,reject) = >{
    //do something 
    // The final success or failure
    // The value can be passed successfully
    resolve(value)
    // Failed To pass in the failure cause
    reject(reason)
    // Note: both values execute one
})
promise().then(success= >{
    // The execution succeeds, that is, success is the value passed in resolve after the execution
    console.log(success)
    return success
},fail= >{
    // Failed to execute, that is, reject after execution fail is the reason passed by reject
    console.log(fail)
})
//. Then a promise object is returned by default, and the return success above can be received by the SUC
.then( suc= > console.log(suc))
.finally(() = >{
    console.log('Executes on success or failure and returns a promise object by default for chained calls.')
})
.catch(err= >{
    // Catch errors during the promise run, wherever the error is
    console.log(err)
})
Copy the code

Example 2: Using promises with async await

    function promise(){
        return new Promise((resolve,reject) = >{
            xxxxx
            resolove('success')})}async function test(){
        // We wait for the promise to complete
        let a = await promise()
        console.log(a) / / success
    }
Copy the code

Example 3: Promise’s other class methods all,race

function a(){
   return new Promise((resolve,reject) = >{
           setTimeout(() = >{
              resolve('a')},1000)})}function b(){
    return 'b'
}
// Receive the parameter -array. The contents of the array are either ordinary characters or a Promise object
// function - waits for everything passed in to complete and returns an array result
Promise.all(['A'.'B',a(),b()]).then(res= >{
    let result = res
    console.log(result) //['A','B','a','b']
})
// Receive the parameter -array. The contents of the array are either ordinary characters or a Promise object
// function - returns the first completed result
Promise.race(['A'.'B',a(),b()]).then(res= >{
    let result = res
    console.log(result) // A
})

Copy the code

Write Promise prepare

Before writing a promise, you need to understand the logic of what it is, what it has, and what it does. Know yourself, know your enemy!

  1. The first way to use a Promise is to know that it is a Promise class by the new keyword, and to pass an executable (function) with two arguments, resolve,reject.
  2. This is very depressing. Once the state is fulfilled, it cannot be changed. This is very depressing
  3. The resolve and reject functions are instance methods and are used to change the state. Resolve: fulfilled reject: rejected
  4. What the then method does internally is determine the state. If the state is successful, call the successful callback, and otherwise call the failed callback, which is defined in the prototype object
  5. Then the success callback has one parameter, indicating the success information, and the failure callback has one reason, indicating the failure cause
  6. The then method is called in chains – returns a Promise object, and the promise can be called several times. Then ()
  7. The THEN method can pass normal values and promise objects, but not the current promise object
  8. Catch an error, throw an error All the places where the function is executed require trycatch to catch an exception and throw it by reject
  9. If no argument is passed to the then, it is automatically passed to the next THEN to determine whether the THEN has an argument. If no value is added to the THEN, => value
  10. The finally instance method returns the result whether it succeeds or fails
  11. Catch catches an error anywhere on the promise chain
  12. The all and RACE class methods (which pass in an array of normal values that wait for all to complete and return a uniform result, or a Promise object that returns a result as soon as one of them is complete)
  13. The resolve method

Simple implementation

Example 1: Write a simple Promise class with 1,2,3,4 wrapped in it

//myPromise.js
// Define global state variables
const PENDING = 'pending'
const FULLFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Define the MyPromise class
class MyPromise {
    constructor(execute){
        // Execute excute immediately and pass in the instance methods resolve and reject
        // This is why promises can run synchronously
        execute(this.resolve,this.reject)
    }
    // Define the state, which defaults to wait
    status = PENDING
    // The result of success
    value = undefined
    // Cause of failure
    reason = undefined
    // Instance method
    // Turn the state into success, and have irreversibility
    resolve = (value) = >{
        if(this.status ! == PENDING)return
        this.status = FULLFILLED
        this.value = value
    }
    // Turn the state into failure, and have irreversibility
    rejecte = (reason) = >{
        if(this.status ! == PENDING)return
        this.status = REJECTED
        this.reason = reason
    }
    // The stereotype method, which passes two callbacks and takes one parameter, the value returned by resolve and reject
    then(successCallback,failCallback){
        if(this.status === FULLFILLED){
            successCallback(this.value)
        }else if(this.status === REJECTED){
            failCallback(this.reason)
        }
    }
}

/ / use
let promsie = new MyPromise((resolve,reject) = >{
    resolve('success')
}).then(res= >{
    console.log(res) / / success
})

Copy the code

Conclusion: This class implements only the basic use of Promise, that is, how to pass parameters and get results through the.then() method, but there is no asynchronous programming method, so there is only a preliminary architecture, the core functionality has not been implemented.

Complete implementation

On the basis of the simple implementation, we focus on improving the various features of the Promise, namely 6-13 points above 6. And a promise can be called multiple times to implement logic (1). Multiple calls to resolve a promise can be saved using the successCallback and failCallback arrays with the two callbacks in the.then and executed through a while loop (2) To resolve a chained call, you need to return a Promise object in the then function, and the chained call can pass arguments, so you need to pass the result of the current callback in the new Promise in resolve and reject

    // In the value and Reason actuators, resolve and reject execute the incoming values
    resolve(successCallback(this.value))
    reject(failCallback(this.reason))
Copy the code

Figure: The following code will be more complete because of other features, so this solution is shown in figure, not the final result

7. The then method can pass normal values and promise objects, but it cannot pass the current Promise object implementation logic

(1) Determine if a promise is a promise, and if it is, execute its.then function. If it is a promise, return it directly.

(2) Determine if the promise object is equal to the current promise object, and if so, throw a type error (if the promise object is returned, an infinite loop call will occur).

(3) according to the above two encapsulate a resolvePromise (promise2, x, resolve, reject) function to do the processing Concrete as shown in figure: the following code will because other functions more perfect, so this solution as shown in figure display, does not represent the final result

8. Catch an error, throw an error. All functions need to be executed by trycatch to catch an exception

9. Parameter passing If no parameter is passed to the THEN, it is automatically passed to the next THEN to determine whether the THEN has parameters. If no parameter is added to the THEN, => value

(1) Pass a callback function with no arguments, and return a Promise for the chained call (2) But it can pass arguments down and it is actually similar to.then (3). The callback function is executed regardless of success or failure

The implementation logic calls this.then(undefined,callback) directly and returns the error anywhere on the promise chain

12. All and race class methods (1), pass in an array of arguments, The contents of the array are either normal values or Promise objects (2), wait for all the Promise objects in the argument to complete the execution and return an array result/have one parameter to finish the execution and return a value (race) (3), it is a Promise class method, and will return a Promise object

13. The resolve class method implements logic (1), passes either a plain value or a Promise object (2), converts the plain value to a Promise object if it is a plain value, or returns it directly if it is a Promise

The final code

//myPromise.js
// Define global state variables
const PENDING = 'pending'
const FULLFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Define the MyPromise class
class MyPromise {
    constructor(execute){
        try{
           // Execute excute immediately and pass in the instance methods resolve and reject
           // This is why promises can run synchronously
           execute(this.resolve,this.reject)
        }catch(e){
            this.reject(e)
        }
    }
    // Define the state, which defaults to wait
    status = PENDING
    // The result of success
    value = undefined
    // Cause of failure
    reason = undefined
    // Save successCallback through an array and loop through it on resolve
    successCallback = []
    // Save failCallback through an array
    failCallback = []
    // Instance method
    // Turn the state into success, and have irreversibility
    resolve = (value) = >{
        if(this.status ! == PENDING)return
        this.status = FULLFILLED
        this.value = value
        while(this.successCallback.length) this.successCallback.shift()(this.value)
    }
    // Turn the state into failure, and have irreversibility
    reject = (reason) = >{
        if(this.status ! == PENDING)return
        this.status = REJECTED
        this.reason = reason
        while(this.failCallback.length) this.failCallback.shift()()
    }
    // The stereotype method, which passes two callbacks and takes one parameter, the value returned by resolve and reject
    then(successCallback,failCallback){
        // Determine if there are arguments, and add a function if there are none
        successCallback = successCallback ? successCallback : value= > value
        failCallback = failCallback ? failCallback : reason= > { throw reason }
        
        let promise2 = new MyPromise((resolve,reject) = >{
            // If you want to use the resolvePromise function, you need to pass in a promise2, and the promisE2 is being created
            if(this.status === FULLFILLED){
                setTimeout(() = >{
                    try{
                        // Get the result of the successful callback and return it to the next Promise
                        let x = successCallback(this.value)
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }
                },0)}else if(this.status === REJECTED){
                setTimeout(() = >{
                    try{
                       // Get the result of the failure callback and return it to the next Promise
                       let x = failCallback(this.reason)
                       resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                       reject(e)
                    }
                   
                },0)}else{
                
                   // The program is in the wait state, so you need to save the callback function in an array and wait for the resolve execution before executing the callback function
                   this.successCallback.push(() = >{
                        try{
                            setTimeout(() = >{ 
                                 let x = successCallback(this.value)
                                resolvePromise(promise2,x,resolve,reject)
                            },0)}catch(e){
                            reject(e)
                        }
                   })
                   this.failCallback.push(() = >{
                       try{
                           setTimeout(() = >{
                               let x = failCallback(this.reason)
                               resolvePromise(promise2,x,resolve,reject)
                           },0)}catch(e){
                           reject(e)
                       }
                   })
            }
        })
        return promise2
    },
    finally(callback){
       return this.then(value= >{
            // Turn the incoming callback function into a Promise asynchronous call and return a Promise object for chained calls
            // The purpose of the transformation is to wait for the current callback function to complete before entering the chained call
            // The finally callback takes no arguments, but passes values from the Promise chain
            return MyPromsie.resolve(callback()).then(() = >value)
        },reason= >{
             return MyPromsie.resolve(callback()).then(() = >{throw reason})
        })
    },
    catch(callback){
        return this.then(undefid,callback)
    },
    static all(array){
        let result = []
        let index = 0
        return new MyPromise((resolve,reject) = >{
            // Add must be placed inside the Promise because it calls resolve
            function add(key,value){
                result[key] = value
                index++
                if(index === array.length){
                    resolve(result)
                }
            }
            // First, loop through the array to determine the type
            for(let i=0; i<array.length; i++){let current = array[i]
                if(current instanceof MyPromise){
                    current.then((value) = >{add(i,value)},reason= > reject(reason))
                }else{
                    add(i,current)
                }
            }
        })
    }
    static race(array){
        let result = []
        return new MyPromise((resolve,reject) = >{
            // Add must be placed inside the Promise because it calls resolve
            function add(key,value){
                result[key] = value
                resolve(result)
            }
            // First, loop through the array to determine the type
            for(let i=0; i<array.length; i++){let current = array[i]
                if(current instanceof MyPromise){
                    current.then((value) = >{add(i,value)},reason= > reject(reason))
                }else{
                    add(i,current)
                }
            }
        })
    }
    static resolve(value){
        if(value instanceof MyPromise) return value
        return new MyPromise(resolve= >{ resolve(value) })
    }
}
// The internal callback function of the then method executes the result comparison function
function resolvePromise(promise2,x,resolve,reject){
    // If the type is equal, throw a type error
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle detected for promise #<promise>'))}// Whether it is a Promise
    if(x instanceof MyPromise){
        // The Promise object calls its THEN method directly
        //x.then(value => resolve(value),reason => reject(reason))
        x.then(resolve,reject)
    }else{
        resolve(x)
    }
}
module.export = MyPromise

Copy the code

Summary: Promise is the most important in JS programming, it is strongly recommended to write the code once, to understand the various functions inside the implementation is also helpful to improve the asynchronous programming ability.