The understanding of the Promise

Promise is an asynchronous programming solution that represents the final completion (or failure) of an asynchronous operation and its resulting value. A Promise object represents a value that is not necessarily known at the time the Promise is created. It allows you to correlate the final 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 future time (from MDN)

What problems Promise solves

  1. Nesting of callbacks causes code to be difficult to maintain and increases code readability
  2. Solve asynchronous problems (e.g., asynchronous method return value and synchronous method processing logic cannot correlate, asynchronous return value correlation)
  3. Controlling concurrent requests

How to fulfill a Promise

The following steps will implement a promise, first defining several core concepts for implementation

  1. Resolve and Reject are implemented
  2. This state is postponed, which is called pending-> depressing or pending-> Rejected. Once the state changes, it cannot be changed
  3. Then method implementation, how to implement the chain callback
  4. Then incoming parameter processing
  5. Then return value processing
  6. Solving asynchronous problems

First, a basic class ‘framework’

Const isFunction = fn => typeof fn === 'function' class MyPromise {constructor(execute) {// Const isFunction = fn => typeof fn === 'function' class MyPromise {constructor(execute) {// Const isFunction = fn => typeof fn === 'function' class MyPromise {constructor(execute) { IsFunction (execute)) {throw new TypeError("params is not Function")} this.initValue(); Use bind to change this to point to, Or use the arrow function to write resolve and reject execute(this.resolve.bind(this), This.reject. Bind (this))} initValue () {this.state = "pending" // Status value this.value = null // Success message this.reason = null // Resolve (value) {console.log(value)} reject (reason) {console.log(reason)}} new MyPromise((resolve, reject ) => { resolve(12) })Copy the code

In this way, we simply implement resolve and reject functions. In order to make the state solidified, we need to add the state determination section in the first step logic of these two functions, that is, processing the state when it is pending, and ignoring the other states

resolve (value) { if (this.state ! == 'pending') return this. Value = value this. State = 'depressing' // Change status value} reject (reason) {if (this. == 'pending') return this.reason = reason this.state = 'rejected' }Copy the code

Then method is the core of Promise. After then method is implemented, most of the other Promise static methods are derivatives of THEN method. Since we want to implement the chain callback of THEN method, You can either return this or re-instantiate the return object, creating a new Promise object that is not handled by the callback function. The new Promise simply accepts as its final state the final state of the original Promise that called the THEN.

then (onFulfilledFun, onRejectedFun) { let { state, reason, value } = this return new MyPromise((resolve, Reject)=> {// if (state === 'pity ') {resolve(ononledfun (value))} // If (state === 'rejected') { reject(onRejectedFun(reason)) } }) }Copy the code

At this point we’ve basically implemented a simple version of Promise, and run the results

new MyPromise(( resolve, reject ) => {
    resolve(12)
}).then(res=> {
    console.log(res)
    return 'a'
}).then(r=> {
    console.log(r)
})
Copy the code

Perfect output:Let’s add a setTimeout to simulate an asynchronous operation and try again

new MyPromise(( resolve, reject ) => {
    setTimeout(()=> {
        console.log('setTimeout')
        resolve(12)
    }, 1000)
}).then(res=> {
    console.log(res)
    return 'a'
}).then(r=> {
    console.log(r)
})
Copy the code

The results are as follows:

Hey!!!!!! My then method is not executed! The reason why this happens is that the occurrence of setTimeout makes the synchronization logic in our THEN function execute first. The calls of the two THEN functions are in a pending state, and the code is as follows

then (onFulfilledFun, onRejectedFun) { let { state, reason, value } = this return new MyPromise((resolve, Reject)=> {// if (state === 'pity ') {resolve(ononledfun (value))} // If (state === 'rejected') {reject(onRejectedFun(reason))} // Call pending status if (state === 'pending') {console.log(123)}})}Copy the code

Output result: After 123 and 123 output twice, the program enters our pending logic. Since the state value is changed in the resolve function, the state has not changed before the set time, so it enters the pending logic. To solve this problem, we use emulated queues, placing our pending operations in a queue and executing them either in resolve or reject

  • The function that stores the queue is declared in the initialization function initValue
This. RejectedQueues = [] // Rejected Queues = [] // Rejected Queues = [Copy the code
  • Fill the THEN function with queue-related operations
then (onFulfilledFun, onRejectedFun) { let { state, reason, value } = this return new MyPromise((resolve, Reject)=> {// if (state === 'pity ') {resolve(ononledfun (value))} // If (state === 'rejected') { reject(onRejectedFun(reason)) } if (state === 'pending') { this.fulfilledQueues.push( value => resolve(onFulfilledFun(value))) this.rejectedQueues.push(value => reject(onRejectedFun(value))) } }) }Copy the code
  • Success output in order:

  • At this point, we haven’t segmented the arguments passed to the then function
  • There are two situations:
  1. Pass in the desired function(Handle: Run function)
  2. Afferent nonfunction(Processing: ignore the incoming value and take the value of the last successful change, that is, value penetration problem)
  • After processing the incoming value, we can also handle the ononledFun or onRejectedFun return value
  • There are three cases:
  1. Return normal value
  2. Return a new Promise or Thenable data structure
  3. Returning itself is a circular reference problem.
  • Next, the then function is transformed
then (onFulfilledFun, onRejectedFun) { let { state, reason, value } = this const _myPromise = new MyPromise((resolve, Reject)=> {const resolveFun = value => {const resolveFun = value => {if (! IsFunction (onledFun)) {resolve(value)} else {let result = onledFun (value); If (result === _myPromise) {throw new TypeError('Loop call, Else if (result instanceof MyPromise) {result.then(resolve, Reject)} else {resolve(result)}}} const rejectFun = value => {if (! IsFunction (onRejectedFun) {reject(value)} else {let result = onRejectedFun(value); If (result === _myPromise) {throw new TypeError('Loop call, Else if (result instanceof MyPromise) {result.then(resolve, reject) } else { reject(result) } } } switch (state) { case "pending": this.fulfilledQueues.push(resolveFun) this.rejectedQueues.push(rejectFun) break; case "fulfilled": onFulfilledFun(value) break; case "rejected": onRejectedFun(reason) break default: throw new Error("nothing") } }) return _myPromise }Copy the code
  • So let’s test that out
new MyPromise(( resolve, reject ) => {
    setTimeout(()=> {
        console.log('setTimeout')
        resolve(12)
    }, 1000)
}).then("abc").then(res=> {
    console.log('then1:',res)
    return new MyPromise(resolve => resolve("then1-1"))
}).then(r=> {
    console.log('then2:', r)
})
Copy the code
  • The result is printed as expected, and the case that the then function argument is not a function is ignored

  • Let’s test the circular reference problem
let p = new MyPromise(( resolve, reject ) => {
    setTimeout(()=> {
        console.log('setTimeout')
        resolve(12)
    }, 1000)
})
let p1 = p.then(r => {
    console.log(123)
    return p1
})
Copy the code
  • Output as expected

Now that we’re done, let’s go back to the setTimieout and sync resolve

new MyPromise(( resolve, reject ) => {
    resolve(12)
}).then("abc").then(res=> {
    console.log('then1:',res)
    return new MyPromise(resolve => resolve("then1-1"))
}).then(r=> {
    console.log('then2:', r)
})
Copy the code
  • The results are as follows

What a mistake!! Then drop then(” ABC “) and print it again

Ah! Then2 has no output! Quickly analyze the code error analysis: The state will be fulfilled, and then(” ABC “) will be called gradually. Then the state will be fulfilled, and the parameters of the THEN function will be non-function. The program will step into the case “depressing”, so an error will be reported

  • Then2 does not print the state value received in the then function

If the first then function executes, the status becomes pending, and the second then operation is in the success queue, but it is not executed, so it will not output. Subsequent chained calls are added to the queue later than the execution of the resolve function. So how do you solve that? Resolve is queued before the chained call is queued. Resolve is queued before the chained call is queued

resolve (value) { const resolveRun = () => { if (this.state ! == 'pending') return this.value = value this.state = 'fulfilled' this.fulfilledQueues.forEach(fn => fn(this.value)) } setTimeout(resolveRun, 0) }Copy the code

Take a look at the output:

As expected, gee, why? This is because the resolve function logic is placed at the end of the event loop, and the chained functions are queued because the state values are pending. When we execute resolve again, the queue array contains the chained functions.

Resolve (thenable); resolve (thenable); resolve (Promise); resolve(new MyPromise(resolve => resolve(‘test’)))

At this point we need to add a judgment to the resolve function

if (value instanceof MyPromise) { value.then(v => { this.value = v this.fulfilledQueues.forEach(fn => fn(v)) }, Err = > {enclosing a tiny = err enclosing rejectedQueues. ForEach (fn = > fn (err))})} / / thenable judgment else if (value && value. Then &&  isFunction(value.then)) { new MyPromise(value.then).then(res=> { this.value = res this.fulfilledQueues.forEach(fn => Fn (value) this.)})} / / other things else {. This value this value =. FulfilledQueues. ForEach (fn = > fn (value))}Copy the code

This allows our resolve function to support multiple types of arguments

new MyPromise(( resolve, reject ) => {

    resolve(new MyPromise(r => r('hello')))
    resolve(thenable)
    resolve(123)
}).then(res=> {
    console.log('then1:',res)
   
})
Copy the code

The code above tests that all three cases output as expected.

  • Now that we have basically implemented the core functions, let’s implement the other methods

. Catch implementation

  • The catch method does not pass the first argument to the then function
catch (onRejected) {
    return this.then(null, onRejected)
}
Copy the code

. Finally realize

Finally describes the portal

Finally (onFinally) {// By getting the constructor, Constructor return this.then(value => pConst. Resolve (onFinally()).then(() => value)), reason => pConst.resolve(onFinally()).then(() => { throw reason }) ) }Copy the code

. The resolve to implement

  • The static resolve method returns a promise-wrapped object with three different arguments
  1. thenable
  2. Promise instance
  3. Common values
Static resolve (value) {// If MyPromise is used, If (Value instanceof MyPromise) return value // If the argument is thenable object if (value && value.then && IsFunction (value.then)) return new MyPromise(value.then) Return new MyPromise(resolve => resolve(value))}Copy the code

. Reject implementation

static reject (err) {
    return new MyPromise((resolve, reject) => reject(err))
}
Copy the code

. All implementation

  • Orderly output
return new MyPromise((resolve, reject) => {
    let len = promiseList.length
    let result = []
   promiseList.reduce((pre, next) => {
        return pre.then(next).then(res => {
            result.push(res)
            result.length === len && resolve(result)
        },error => reject(error))
    }, MyPromise.resolve())
})
Copy the code
  • Chaotic output
return new MyPromise((resolve, reject) => {
    let len = promiseList.length
    let result = []
    for (let i = 0; i < len ; i++) {
        let pItem = promiseList[i]
        pItem.then(res => {
            result.push(res)
            result.length === len && resolve(result)
        }, error => {
            reject(error)
        })

    }
})
Copy the code

The race to achieve

static race (promiseList) {
    return new MyPromise((resolve, reject) => {
        promiseList.forEach(item => {
            item.then(res=> resolve(res), error => reject(error))
        })
    })
}   
Copy the code

At this point, most of the functionality has been implemented. Here is the complete code:

Const isFunction = fn => typeof fn === 'function' class MyPromise {constructor(execute) {const isFunction = fn => typeof fn === = 'function' class MyPromise {constructor(execute) { IsFunction (execute)) {throw new TypeError("params is not Function")} this.initValue(); Use bind to change this to point to, Or use the arrow function to write resolve and reject execute(this.resolve.bind(this), This.reject. Bind (this))} initValue () {this.state = "pending" // Status value this.value = null // Success message this.reason = null // This. RejectedQueues = [] // Rejected queues} loopFun (queues, v) { queues.forEach(fn => fn(v)) } resolve (value) { const resolveRun = () => { if (this.state ! If (value instanceof MyPromise) {value. Then (v => { this.value = v this.loopFun(this.fulfilledQueues, v) }, err => { this.reason = err this.loopFun(this.rejectedQueues, If (value && value. Then && isFunction(value. Then)) {new MyPromise(value.then). Then (res=> {  this.value = res this.loopFun(this.fulfilledQueues, Else {this.value = value this.loopFun(this.implemented ledqueues, value)}} setTimeout(resolveRun, resolveRun) 0) } reject (reason) { const rejectRun = () => { if (this.state ! == 'pending') return this.reason = reason this.state = 'rejected' this.rejectedQueues.forEach(fn => fn(this.reason)) } setTimeout(rejectRun, 0) } then (onFulfilledFun, onRejectedFun) { let { state, reason, value } = this const _myPromise = new MyPromise((resolve, Reject)=> {const resolveFun = value => {// If (! IsFunction (onledFun)) {resolve(value)} else {let result = onledFun (value); If (result === _myPromise) {throw new TypeError('Loop call, Else if (result instanceof MyPromise) {result.then(resolve, Reject)} else {resolve(result)}}} const rejectFun = value => {if (! IsFunction (onRejectedFun) {reject(value)} else {let result = onRejectedFun(value); If (result === _myPromise) {throw new TypeError('Loop call, Else if (result instanceof MyPromise) {result.then(resolve, reject) } else { reject(result) } } } switch (state) { case "pending": this.fulfilledQueues.push(resolveFun) this.rejectedQueues.push(rejectFun) break; case "fulfilled": onFulfilledFun(value) break; case "rejected": onRejectedFun(reason) break default: throw new Error("nothing") } }) return _myPromise } catch (onRejected) { return this.then(null, OnRejected)} finally (onFinally) {// Constructor return this.then(value => pConst. Resolve (onFinally()).then(() => value)), Reason => const. Resolve (onFinally()).then(() => {throw reason}))} static resolve(value) {// If (Value instanceof MyPromise) return value // If the argument is thenable object if (value && value.then && IsFunction (value.then)) return new MyPromise(value.then) Return new MyPromise(resolve => resolve(value))} static all (promiseList) {// promiseList returns new MyPromise((resolve, reject) => { let len = promiseList.length let result = [] for (let i = 0; i < len ; i++) { let pItem = promiseList[i] pItem.then(res => { result.push(res) result.length === len && resolve(result) }, Error => {reject(error)})}}) reject) => { // let len = promiseList.length // let result = [] // promiseList.reduce((pre, next) => { // return pre.then(next).then(res => { // result.push(res) // result.length === len && resolve(result) // },error => reject(error)) // }, MyPromise.resolve()) // }) } static race (promiseList) { return new MyPromise((resolve, reject) => { promiseList.forEach(item => { item.then(res=> resolve(res), error => reject(error)) }) }) } }Copy the code

The overall code still has room for optimization, and please correct any misunderstandings. The next article introduces the interview questions related to promise

  • Next up: # Understand the Promise thoroughly