Writing in the front

The “callback hell” problem can be solved by chaining the Promise. This article mainly interprets the Promise/A+ specification and realizes A Promise on the basis of this specification.

Promise/A + specification

An open, robust, and universal JavaScript Promise standard. Developed by developers for their reference. The Promise/A+ specification is A unified specification for all the Promise libraries in the industry.

Promise/A + standard English document: promisesaplus.com/ Promise/A + specification (translation) Turing translation: www.ituring.com.cn/article/665…

The use of the Promise

Create an instance of Promise:

new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('success')
    reject('fail')},1000)
  console.log('Create a Promise')
})
  .then(
    (value) = > {
      console.log('111')},(reason) = > {
      console.log('222')
    }
  )
  .then((value) = > {
    console.log('333')})// Create a promise
// success
/ / 333
Copy the code
  • The Promise instance of new accepts an executor function that takes a method with two arguments on success and failure, and the executor function executes immediately.
  • Only resolve is output, while reject is not output. That is, the Promise has a – state and the state can only be determined by pending-> depressing or pending-> Rejected, which is irreversible
  • Promise supports chained programming, which means that the THEN method returns a Promise instance.

Make good on your Promise

state

We all know that promises have three states

  • Pending state

In the wait state, a Promise must meet the following conditions: – It can be migrated to the execution or rejection state

  • This is a big pity.

In the execution state, a promise must satisfy the following conditions: – It cannot be migrated to any other state – it must have an immutable final value

  • (Rejected)

In the rejection state, a promise must satisfy the following conditions: – It cannot be migrated to any other state – it must have an immutable source

// Promise three states
const PENDING = 'pending' / / wait for
const FULFILLED = 'fulfilled' / / success
const REJECTED = 'rejected' / / fail

class Promise {
  constructor(executor) {
    this.status = PENDING // Default pending state
    this.value = undefined // The value passed successfully
    this.reason = undefined // The cause of the incoming failure
    // The successful value will be FULFILLED
    let resolve = (value) = > {
      If status changes, it will not be changed again. It is either a success state or a failure state
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
      }
    }
    // Failed Failed cause Status changes to REJECTED
    let reject = (reason) = > {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
      }
    }
    try {
      executor(resolve, reject) // The default executor executes immediately
    } catch (e) {
      reject(e) // An error is reported and an exception is thrown}}}Copy the code

then()

A promise must provide a THEN method to access its current value, final value, and reason.

  • Eventual Value: The so-called final value refers to the value transferred to the solution callback when the promise is solved. Since the promise has the characteristics of one-off, when this value is passed, it marks the end of the promise waiting state, so it is called the final value, sometimes directly referred to as value.
  • Reason: The rejection reason, the value passed to the rejection callback when a promise is rejected
  • Promise’s then method accepts two arguments:promise.then(onFulfilled, onRejected)
  • OnFulfilled and onRejected are both optional parameters
  • The THEN method can be called more than once by the same PROMISE, and must return a Promise object
then(onFulfilled, onRejected) {
    // then accepts a successful callback and a failed callback for two arguments
    // Parameters are optional
    onFulfilled =
      typeof onFulfilled === 'function'
        ? onFulfilled
        : (data) = > {
            data
          }
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (error) = > {
            throw error
          }
    // The then method can be called more than once by the same promise to return a new promise
    let promise2 = new Promise((resolve, reject) = > {
      if (this.status === FULFILLED) {
        let x = onFulfilled(this.value)
        resolve(x)
      }
      if (this.status === REJECTED) {
        let x = onRejected(this.reason)
        reject(x)
      }
    })
    The then method must return a Promise object
    return promise2
  }
Copy the code

At this point, a simple promise is made, but we’re all dealing with synchronous cases, so what about asynchronous cases? So let’s keep going

Implement asynchronous promises

Let’s start by looking at when the following code changes state

new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('111111111') 
    // Whenever the user calls resolve or reject, the state of the Promise has changed.
  }, 1000)
}).then(
  (res) = > {
    console.log('res', res)
  },
  (err) = > {
    console.log('err', err)
  }
)
Copy the code
  • A call to resolve or Reject indicates that the state of the Promise has changed, so we execute the incoming callback after the state changes, optimizing the resolve and Reject methods in the constructor
	this.onFulfilledCbs = [] // Store an array of successful callbacks to then
    this.onRejectedCbs = [] // Store an array of then failed callbacks
    // The successful value will be FULFILLED
    let resolve = (value) = > {
      If status changes, it will not be changed again. It is either a success state or a failure state
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
        // Execute in sequence after the state changes
        this.onFulfilledCbs.forEach((fn) = > fn())
      }
    }
    // Failed Failed cause Status changes to REJECTED
    let reject = (reason) = > {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
        // Execute in sequence after the state changes
        this.onRejectedCbs.forEach((fn) = > fn())
      }
    }
Copy the code

In the then function

  • If there is a pity or Rejected state, execute the callback function directly
  • If the state is pending, push the callback function onto the stack and wait for the state to change before executing the incoming callback
  • If ondepressing or onRejected throws an exception E, promise2 must reject the implementation and return the rejection cause E

  then(onFulfilled, onRejected) {
    // then accepts a successful callback and a failed callback for two arguments
    // Parameters are optional
    onFulfilled =
      typeof onFulfilled === 'function'
        ? onFulfilled
        : (data) = > {
            data
          }
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (error) = > {
            throw error
          }
    // The then method can be chained to return a new Promise
    let promise2 = new Promise((resolve, reject) = > {
      if (this.status === FULFILLED) {
        // If ondepressing or onRejected throws an exception (e), promise2 must reject and return rejection (e)
        try {
          let x = onFulfilled(this.value)
          resolve(x)
        } catch (e) {
          // Throw an exception, e, and return reject e
          reject(e)
        }
      }
      if (this.status === REJECTED) {
        try {
          let x = onRejected(this.reason)
          reject(x)
        } catch (e) {
          reject(e)
        }
      }
      if (this.status === PENDING) {
     	// Subscribe if asynchronous
        this.onFulfilledCbs.push(() = > {
          try {
            let x = onFulfilled(this.value)
            resolve(x)
          } catch (e) {
            reject(e)
          }
        })
        this.onRejectedCbs.push(() = > {
          try {
            let x = onRejected(this.reason )
            reject(x)
          } catch (e) {
            reject(e)
          }
        })
      }
    })
    The then method must return a Promise object
    return promise2
  }
Copy the code

Promise resolution process, resolvePromise function

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.

This treatment of thenables allows promise implementations to interoperate, Assimilate as long as they expose a Promises/ a +-compliant then method. It also allows Promises/ a + Implementations to “Assimilate” nonconformant implementations with reasonable then methods.

The Promise resolution process is an abstract operation that requires input of a Promise and a value, which we express as [[Resolve]] (Promise, x). If x is possible, try to make the Promise adopt the state of X on the assumption that X will behave at least like the Promise. Otherwise, it will fulfill its promise with the value x. This treatment of implementables makes Promises interoperable, as long as they expose THEN methods that conform to Promises/A +. It also allows Promises/A + implementations to “integrate” substandard implementations using sound THEN methods.

Promise resolution process

To run [[Resolve]](promise, x), follow these steps:

  • X is equal to promise

If a promise and x point to the same object, reject the promise as TypeError grounds

  • X to Promise
    • If x is a Promise, make the Promise accept the state of x:
      • If X is in wait state, the promise needs to remain in wait state until x is executed or rejected
      • If x is in the execution state, execute the promise with the same value
      • If X is in the reject state, reject the promise with the same grounds
  • X is an object or function
    • Assign x. teng to then
    • If an error e is thrown when taking the value x. teng, reject the promise based on e
    • If then is a function, x is called as the function’s scope this. Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise:
      • If resolvePromise is called with the value y, run [[Resolve]](promise, y)
      • If rejectPromise is invoked with argument r, reject the promise with argument r
      • If both resolvePromise and rejectPromise are invoked, or if the same parameter is invoked more than once, the first call is preferred and the remaining calls are ignored
    • If calling the then method raises exception e:
      • If a resolvePromise or rejectPromise has already been invoked, it is ignored
      • Otherwise, reject the promise based on e
  • If then is not a function, execute the promise with an x argument
  • If x is not an object or function, execute the promise with x as an argument

Implementation of the resolvePromise function

The Promise resolution process is pulled away and implemented with the resolvePromise function

The then method must return a promise object called promise2. ResolvePromise () takes the current promise and its resolve Reject callback. Use setTimeout to delay execution to get promise2

then

then(onFulfilled, onRejected){...let promise2 = new Promise((resolve, reject) = >{...if (this.status === FULFILLED) {
        // If ondepressing or onRejected throws an exception (e), promise2 must reject and return rejection (e)
        setTimeout(() = > {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)}... })...The then method must return a Promise object
    return promise2
}
Copy the code

resolvePromise

//resolvePromise is implemented according to the PromiseA+ specification
const resolvePromise = (promise2, x, resolve, reject) = > {
  // Call restriction of promise2 prevents multiple calls from succeeding and failing
  If both resolve and reject are called, or if the same argument is called more than once, the first call takes precedence and the rest is ignored
  let called
  if (x === promise2) {
    reject(
      // If a promise and x point to the same object, reject the promise as TypeError
      new TypeError('Chaining cycle detected for Promise #<Promise>'))}if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    // x is an object or function
    try {
      // Assigning x. teng to then ensures that the value of the next then cannot be used
      let then = x.then()
      if (typeof then === 'function') {
        // then If it is a function to prevent then methods from being defined by Object.defineProperty, need to be fault-tolerant
        // Call THEN directly and throw resolve with the current successful call as resolve, and reject with the current failed Promise if it fails
        then.call(
          (y) = > {
            if (called) {
              return
            }
            called = true
            // y may still be a Promise, recursively calling resolvePromise until the resolved value is a normal value/object
            resolvePromise(promise2, y, resolve, reject)
          },
          (r) = > {
            if (called) {
              return
            }
            called = true
            reject(e)
          }
        )
      } else {
        // If x is a normal object let promise2 succeed
        resolve(x)
      }
    } catch (e) {
      if (called) {
        return
      }
      called = true
      // If an exception is thrown, the failure result is passed down
      reject(e)
    }
  } else {
    // x is a normal value that lets promise2 succeed
    resolve(x)
  }
}
Copy the code

Conclusion:

  • If there is a return value for success or failure in then, the success of the next THEN will be followed
  • If the first promise returns a normal value, the next successful callback for then is entered; If the first promise returns a new promise, wait for the result of the return promise execution to be passed to the next THEN

Up to this compliance with the Promise A+ specification Promise is basically completed!

defer

What defer does:

  • It does not require new to generate promise instances, optimizing the issue of promise nesting
  • Promises – aplus-Tests need to write a method 🙈

The realization of the defer

/ / expansion of the API
// Resolve nesting
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) = > {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
Copy the code

Use of defer:

function a() {
  let defer = Promise.defer()
  defer.resolve('success')
  // defer.reject('fail')
  return defer.promise
}
a().then(
    (value) = > {
      console.log(value)
    },
    (reason) = > {
      console.log(reason)
    }
  )
  .then((value) = > {
    console.log(value)
  })
Copy the code

Promise specification testing

npm install promises-aplus-tests -g
Copy the code

Go to the promise.js directory and run it

promises-aplus-tests ./Promise.js
Copy the code

The test results

Extend Promise to five static methods and two instance methods

A static method

resolve reject all allSettled race

Promise.resolve

Promise.resolve(‘foo’) equals new Promise(resolve => resolve(‘foo’))

Promise.resolve = function (value) {
  // value can not be returned directly if it is a promise, so call then
  if (value instanceof Promise) {
    try {
      let then = value.then
      return new Promise(then.bind(value))
    } catch (error) {
      return new Promise((resolve, reject) = > {
        reject(error)
      })
    }
  } else {
    return new Promise((resolve, reject) = > {
      resolve(value)
    })
  }
}
Copy the code

Promise.reject

Promise.reject = (reason) = > {
  return new Promise((resolve, reject) = > {
    reject(reason)
  })
}
Copy the code

Promise.all

Promise. All receives an array of Promise instances, which is fulfilled. After the incoming Promise instances are fulfilled, the array of corresponding results will be returned according to the order passed in by the Promise instance, if there is a Rejected, the value of the first rejected state will be returned

Promise.all = function (values) {
  return new Promise((resolve, reject) = > {
    let arr = []
    let i = 0
    const valueNum = values.length
    function resolveData(data, index) {
      arr[index] = data
      if (++i === valueNum) {
        resolve(arr)
      }
    }
    values.forEach((value, index) = > {
      try {
        if (value instanceof Promise) {
          // Determine if the value in the array is a promise
          // yes: success status = "takes its value and pushes it into the corresponding array of results
          // Failure status = reject Failure cause
          // no: when normal values are pushed into the corresponding array of results
          value.then(
            (res) = > {
              resolveData(res, index)
            },
            (err) = > {
              reject(err)
            }
          )
        } else {
          resolveData(value, index)
        }
      } catch (error) {
        reject(error)
      }
    })
  })
}

Copy the code

Promise. The use of all

const p1 = new Promise((resolve, reject) = > {
  setTimeout(() = > resolve('111'), 0)})const p2 = new Promise((resolve, reject) = > {
  // setTimeout(() => reject('222'), 0)
  setTimeout(() = > resolve('222'), 0)})Promise.all([p1, p2, '123']).then(
  (value) = > {
    console.log(value) / / [' 111 ', '222', '123')
  },
  (err) = > {
    console.log(err) // 222 (p2 comments open output here)})Copy the code

Promise.allSettled

Promise.allsettled () method returns a Promise after all the given promises have fulfilled or rejected, with an array of objects, each representing the corresponding Promise result. For each result object, They all have a status string. If its value is fulfilled, then there is a value on the result object. If the value is rejected, there is a reason. Value/Reason reflects the value of each promise resolution/rejection.

Promise.allSettled = (values) = > {
  const result = values.map((p) = > {
    return Promise.resolve(p).then(
      (value) = > {
        return { status: 'fulfilled', value }
      },
      (reason) = > {
        return { status: 'rejected', reason }
      }
    )
  })
  return Promise.all(result)
}
Copy the code

The use of Promise. AllSettled

const promise1 = Promise.resolve(3)
const promise2 = new Promise((resolve, reject) = >
  setTimeout(reject, 100.'foo'))const promises = [promise1, promise2]

Promise.allSettled(promises).then((results) = > {
  results.forEach((result) = > console.log(result))
})
// { status: 'fulfilled', value: 3 }
// { status: 'rejected', reason: 'foo' }
Copy the code

Promise.race

The promise.race method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected. The fastest result is returned, regardless of whether the result itself is a success state or a failure state.

Promise.race = (values) = > {
  return new Promise((resolve, reject) = > {
    values.forEach((p) = > {
      p.then(
        (value) = > {
          resolve(value)
        },
        (reason) = > {
          reject(reason)
        }
      )
    })
  })
}
Copy the code

Promise. The use of the race

const promise1 = new Promise((resolve, reject) = > {
  setTimeout(resolve, 500.'one')})const promise2 = new Promise((resolve, reject) = > {
  setTimeout(resolve, 100.'two')})Promise.race([promise1, promise2]).then((value) = > {
  console.log(value)
  // one
})
Copy the code

Instance methods

catch finally

catch

The catch() method returns a Promise and handles the rejection. It behaves the same as calling promise.prototype. then(undefined, onRejected)

catch(fn) { 
   return this.then(undefined, fn)
}
Copy the code

The use of the catch

var p1 = new Promise(function (resolve, reject) {
  resolve('Success')
})

p1.then(function (value) {
  console.log(value) // "Success!"
  throw 'oh, no! '
})
  .catch(function (e) {
    console.log(e) // "oh, no!"
  })
  .then(
    function () {
      console.log('after a catch the chain is restored') // after a catch the chain is restored
    },
    function () {
      console.log('Not fired due to the catch')})Copy the code

finally

The finally() method returns a Promise. At the end of the promise, the specified callback function will be executed, whether the result is fulfilled or Rejected. This provides a way for code to be executed whether or not a Promise completes successfully, avoiding situations where the same statement needs to be written once in both then() and catch().

finally(fn) {
    return this.then(
      (data) = > {
        return Promise.resolve(fn()).then(() = > {
          data
        })
      },
      (err) = > {
        return Promise.resolve(fn()).then(() = > {
          throw err
        })
      }
    )
  }
Copy the code

Finally the use of

var promise = new Promise(function (resolve, reject) {
  resolve('error')
})
  .then(() = > {
    console.log('success') // success
  })
  .catch(() = > {
    console.log('catch')
  })
  .finally(() = > {
    console.log('finally') // finally
  })
Copy the code

Well, that’s our Promise, so if you have something else to add or if you think I’ve made a mistake, feel free to discuss it in the comments section. 😁


The source is available at ❤️

const PENDING = 'pending' / / wait for
const FULFILLED = 'fulfilled' / / success
const REJECTED = 'rejected' / / fail

//resolvePromise is implemented according to the Promise+ specification
const resolvePromise = (promise2, x, resolve, reject) = > {
  // Call restriction of promise2 prevents multiple calls from succeeding and failing
  If both resolve and reject are called, or if the same argument is called more than once, the first call takes precedence and the rest is ignored
  let called
  if (x === promise2) {
    reject(
      // If a promise and x point to the same object, reject the promise as TypeError
      new TypeError('Chaining cycle detected for Promise #<Promise>'))}if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    // x is an object or function
    try {
      // Assigning x. teng to then ensures that the value of the next then cannot be used
      let then = x.then()
      if (typeof then === 'function') {
        // then If it is a function to prevent then methods from being defined by Object.defineProperty, need to be fault-tolerant
        // Call THEN directly and throw resolve with the current successful call as resolve, and reject with the current failed Promise if it fails
        then.call(
          (y) = > {
            if (called) {
              return
            }
            called = true
            // y may still be a Promise, recursively calling resolvePromise until the resolved value is a normal value/object
            resolvePromise(promise2, y, resolve, reject)
          },
          (r) = > {
            if (called) {
              return
            }
            called = true
            reject(r)
          }
        )
      } else {
        // If x is a normal object let promise2 succeed
        resolve(x)
      }
    } catch (e) {
      if (called) {
        return
      }
      called = true
      // If an exception is thrown, the failure result is passed down
      reject(e)
    }
  } else {
    // x is a normal value that lets promise2 succeed
    resolve(x)
  }
}
class Promise {
  constructor(executor) {
    this.status = PENDING // Default pending state
    this.value = undefined // The value passed successfully
    this.reason = undefined // The cause of the incoming failure
    this.onFulfilledCbs = [] // Store an array of successful callbacks to then
    this.onRejectedCbs = [] // Store an array of then failed callbacks
    // The successful value will be FULFILLED
    let resolve = (value) = > {
      If status changes, it will not be changed again. It is either a success state or a failure state
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
        // Execute in sequence after the state changes
        this.onFulfilledCbs.forEach((fn) = > fn())
      }
    }
    // Failed Failed cause Status changes to REJECTED
    let reject = (reason) = > {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
        // Execute in sequence after the state changes
        this.onRejectedCbs.forEach((fn) = > fn())
      }
    }
    try {
      executor(resolve, reject) // The default executor executes immediately
    } catch (e) {
      reject(e) // An error is reported and an exception is thrown}}then(onFulfilled, onRejected) {
    // then accepts a successful callback and a failed callback for two arguments
    // Parameters are optional
    onFulfilled =
      typeof onFulfilled === 'function'
        ? onFulfilled
        : (data) = > {
            data
          }
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (error) = > {
            throw error
          }
    // The then method can be called more than once by the same promise to return a new promise
    let promise2 = new Promise((resolve, reject) = > {
      if (this.status === FULFILLED) {
        // If ondepressing or onRejected throws an exception (e), promise2 must reject and return rejection (e)
        setTimeout(() = > {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)}if (this.status === REJECTED) {
        setTimeout(() = > {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)}if (this.status === PENDING) {
        this.onFulfilledCbs.push(() = > {
          setTimeout(() = > {
            try {
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)})this.onRejectedCbs.push(() = > {
          setTimeout(() = > {
            try {
              // let x = onRejected(this.value)
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)})}})The then method must return a Promise object
    return promise2
  }
  catch(fn) {
    return this.then(undefined, fn)
  }
  finally(fn) {
    return this.then(
      (data) = > {
        return Promise.resolve(fn()).then(() = > {
          data
        })
      },
      (err) = > {
        return Promise.resolve(fn()).then(() = > {
          throw err
        })
      }
    )
  }
}
/ / expansion of the API
// Resolve nesting
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) = > {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
Promise.resolve = function (value) {
  if (value instanceof Promise) {
    try {
      let then = value.then
      return new Promise(then.bind(value))
    } catch (error) {
      return new Promise((resolve, reject) = > {
        reject(error)
      })
    }
  } else {
    return new Promise((resolve, reject) = > {
      resolve(value)
    })
  }
}
Promise.reject = (reason) = > {
  return new Promise((resolve, reject) = > {
    reject(reason)
  })
}
Promise.all = function (values) {
  return new Promise((resolve, reject) = > {
    let arr = []
    let i = 0
    const valueNum = values.length
    function resolveData(data, index) {
      arr[index] = data
      if (++i === valueNum) {
        resolve(arr)
      }
    }
    values.forEach((value, index) = > {
      try {
        if (value instanceof Promise) {
          // Determine if the value in the array is a promise
          // yes: success status = "takes its value and pushes it into the corresponding array of results
          // Failure status = reject Failure cause
          // no: when normal values are pushed into the corresponding array of results
          value.then(
            (res) = > {
              resolveData(res, index)
            },
            (err) = > {
              reject(err)
            }
          )
        } else {
          resolveData(value, index)
        }
      } catch (err) {
        reject(err)
      }
    })
  })
}
Promise.allSettled = (values) = > {
  const result = values.map((p) = > {
    return Promise.resolve(p).then(
      (value) = > {
        return { status: 'fulfilled', value }
      },
      (reason) = > {
        return { status: 'rejected', reason }
      }
    )
  })
  return Promise.all(result)
}
Promise.race = (values) = > {
  return new Promise((resolve, reject) = > {
    values.forEach((p) = > {
      p.then(
        (value) = > {
          resolve(value)
        },
        (reason) = > {
          reject(reason)
        }
      )
    })
  })
}
module.exports = Promise
Copy the code