Promises are the most common syntax in development, and most asynchronous processing is done through promises. There are A number of Promise specifications, and ES6 eventually uses the Promise/A+ specification, so the following code is basically based on that specification.

Let’s start by listing all of the Promise’s instance methods and static methods

Instance methods

  • then: new Promise((resolve, reject) => {... }). Then (() = > {the console. The log (' rsolve success callback ')}, () = > {the console. The log (' failed to reject the callback ')})
  • catch: new Promise((resolve, reject) => {... }). Catch (() => {console.log('reject failed methods ')}).
  • finally: new Promise((resolve, reject) => {... }).finally(() => {console.log(' Success and failure enter ')})
  • All of the above method calls will return a newPromise

A static method

  • resolve: Promise.resolve(value)returnPromiseThe instance
  • reject: Promise.reject(value)returnPromiseThe instance
  • all: Promise.all(promises): Passed in array formatPromiseAnd return the newPromiseInstance, the values are returned sequentially on success, and one of the failures becomes a direct failure
  • race: Promise.race(promises): Passed in array formatPromiseAnd return the newPromiseFor example, success or failure depends on the way the first one is completed

Once the Promise state is determined to be irrevocable, it has the following three states: Pending, Fulfilling and Rejected Promises are fulfilled in the browser by placing them in the microtask queue, which requires processing of the microtask (Event Loop mechanism in JavaScript).

1. Declare instance methods for Promise

class Promise { _value _state = 'pending' _queue = [] constructor(fn) { if (typeof fn ! == 'function') { throw 'Promise resolver undefined is not a function' } /* new Promise((resolve, reject) => { resolve: Successful reject: }) */ fn(this._resolve. Bind (this), this._reject. Bind (this))}) (Fulfilling, OnRejected) {Fulfilling, OnRejected {Fulfilling, OnRejected; This is fulfilled for what it will be. This is fulfilled for what it will be. The Promise can be fulfilled ahead of time, and then the method will be recorded after this. .(this._value) return} // reject if (this._state === 'rejected') {onRejected? .(this._value) return} // Promise is not completed, push to a queue, and when it is completed, This._queue.push({onFulfilled, onRejected, })} // Rejected callback {// Rejected callback this.then(null, null, null); Const Fn = () => onDone() this.then(Fn, const Fn = () {const Fn = () => onDone() this.then(Fn,) If (this._state!) {if (this._state!) {if (this._state!); Fulfill this._state = 'fulfilled' // Fulfill this._state = 'fulfilled' // Fulfill this. This._value = value // Implements the push function argument in the previous.then method, so that the corresponding method can be executed. this._queue.forEach((callback) => { callback.onFulfilled? .(this._value)})} // reject(reject(error) {// reject(this._value)}) {if (this._state! == 'pending') return this._state = 'rejected' this._value = error this._queue.forEach((callback) => { callback.onRejected? .(this._value) }) } }

Invoking logic:

  1. throughthenMethod passes an argument in the form of a function, i.eonFulfilled= >then((onFulfilled, onRejected) => {... })
  2. inthenMethods in turnonFulfilledFunction in a_queueIn this set. = >this._queue.push({ onFulfilled, onRejected })
  3. Wait for the asynchronous callback to complete and executeresolveFunction, called at this point_queueCollect throughthenMethod to register the function. Execute these functions uniformly so that the asynchronous callback is complete, and execute the correspondingthenThe function inside the method
Const p = new Promise((resolve, reject) => {setTimeout(() => {resolve('success')}, 1000) }) p.then((res) => { console.log(res) // => success }) // reject const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject('fail') }, 1000) }) p1.catch((res) => { console.log(res) // => fail }) // finally const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) p2.finally(() => { console.log('done') // => done })

Online Code Demo

2. Microtasking and returning Promises

A. Do microtasks

In the browser, the Promise will be pushed into the microtask when it completes, so we’ll need to do that as well. With MutationObserver in the browser, Node can use process.nextTick

class Promise { ... // Push the microtask _nextTick(fn) {if (typeof mutationObserver! == 'undefined') {// Browsers use MutationObserver to implement microtasks // This can be shared separately to avoid unnecessary overhead, as it would need to generate nodes each time. const observer = new MutationObserver(fn) let count = 1 const textNode = document.createTextNode(String(count)) observer.observe(textNode, { characterData: true }) textNode.data = String(++count) } else if (typeof process.nextTick ! == 'undefined') {process.nextTick(fn)} else {setTimeout(fn, fn); If (this._state!) {if (this._state!) {if (this._state! This._nextTick(() = BB0 {this._state = 'Fulfilling' this._value = value this._queue.forEach((callback) => { callback.onFulfilled? .(this._value)})})} // reject reject(error) {// if (this._state!) {// reject reject(error) {// if (this._state! == 'pending') return this._nextTick(() => {this._state = 'rejected' this._value = error this._queue.forEach((callback) => { callback.onRejected? .(this._value) }) }) } ... }

Results demonstrate

B. Returns a Promise for a chained call

Typically, Promises handle multiple asynchronous requests, sometimes with dependencies between requests.

Such as:

const getUser = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ userId: '123' }) }, 500) }) } const getDataByUser = (userId) => { return new Promise((resolve, reject) => { setTimeout(() => { // .... resolve({a: 1}}, Then ((res) => {console.log(res)// {a: = 0; b: = 0; 1}})

GetDatabyUser relies on the user information returned from the request by GetUser, which requires a Promise chain call. Now let’s change our code

class Promise { constructor(fn) { fn(this._resolve.bind(this), this._reject.bind(this)) } ... // When a Promise is returned, the next Promise method will need to return a new Promise, because the next Promise method will need to be chained and the next Promise method will accept the value of the last Promise method. The returned Promise must be a new Promise, otherwise it will share the state and return the result. // Fulfill the Promise(); // Fulfill the Promise(); // Fulfill the Promise(); Reject) => {// may have resolved, This is very depressing because the Promise can be fulfilled ahead of time, and then it can be recorded behind the then method. If (this._state === 'Fulfilled' && Onfulfilled) { this._nextTick(onFulfilled.bind(this, this._value)) return } if (this._state === 'rejected' && onRejected) { this._nextTick(onRejected.bind(this, This._value)) return} /* Associate the current Promise's then method with the new Promise's resolve and reject. This will relate the onFulfilled in the last Promise to the resolved in the new Promise, which can then be used for something like assignment. */ this._queue. Push ({onFulfilled, onRejected, onRejected})})}) {this._queue. Push ({onFulfilled, onRejected, onRejected})}) { If (this._state! == 'pending') return // The example above returns a Promise instead of a value, so we need to do a special handling here. // is the value of resolve(). If it is an object of the Promise, we need to resolve the result of the Promise, If (typeof value === 'object' &&typeof value.then === 'function') {if (typeof value === 'object' &&typeof value.then === 'function') {if (typeof value === 'object' &&typeof value.then === 'function') { Because the arguments in the then method, once the next Promise resolves, execute the arguments in the then method and pass in the corresponding values. // This._resove => obj.onFulfilled? .(this._value) // this._reject => obj.onRejected? .(this._value) value.then(this._resolve.bind(this), This._reject. Bind (this)) return} this._nextTick(() => {this._state = 'fulfilled' this This._queue.forEach((obj) => {// Fulfilling the return value const val = obj.onFulfilled? .(this._value) // Reoslve, which is fulfilled as the first parameter in the current Promise Then method: Promise.then((res) => {consolle.log(res)}) // console.resolve Promise obj.resolve(val)})}... Promise obj.resolve(val)})}... }

Results demonstrate

Invoking logic:

  1. Microtask adoptionMutationObserverwithprocess.nextTickTo implement
  2. PromiseIt’s called chained. It’s called chainedthenIn the method(onFulfilled, onRejected)Parameter with the newly returnedPromiseIn the(resolve, reject)Relate to each other.
  3. Once the last onePromiseOn success, callonFulfilledDelta function, you can take delta functiononFulfilledPut the value returned in the resolve of the new Promise.
  4. If you encounterresolveThe value isPromiseObject, recursively parses it, and then returns the value

The complete code

class Promise { _value _state = 'pending' _queue = [] constructor(fn) { if (typeof fn ! == 'function') { throw new Error('Promise resolver undefined is not a function') } /* new Promise((resolve, Reject) => {resolve: successful reject: }) */ fn(this._resolve. Bind (this), this._reject. Bind (this))}) Fulfill (onFulfill, onRejected) {// Fulfill (onFulfill, onRejected) {// Fulfill (onFulfill, onRejected) {// Fulfill (onFulfill, onRejected) {// Fulfill (onFulfill, onRejected); This is very depressing because the Promise can be fulfilled ahead of time, and then it can be recorded behind the then method. If (this._state === 'Fulfilled' && Onfulfilled) { this._nextTick(onFulfilled.bind(this, this._value)) return } if (this._state === 'rejected' && onRejected) { this._nextTick(onRejected.bind(this, This._value)) return} // Add the arguments of the current Promise's then method to the new Promise's resolve, reject; This._queue.push({onFulfilled, onRejected, resolve, Reject})})} // reject (reject) {return this.then(null, reject)})})} OnDone) {return this.then((value) = bb0 {onDone() return value}, (value) => {// console.log(value) onDone() throw value})} // Push the microtask _nextTick(fn) {if (typeof MutationObserver! == 'undefined') {// Browser // This can be used separately to avoid unnecessary overhead, as it would need to generate nodes each time. const observer = new MutationObserver(fn) let count = 1 const textNode = document.createTextNode(String(count)) observer.observe(textNode, { characterData: true }) textNode.data = String(++count) } else if (typeof process.nextTick ! == 'undefined') {// Node process.nextTick(fn)} else {setTimeout(fn, 0)}} If (this._state! == 'pending') return // The example above returns a Promise instead of a value, so we need to do a special handling here. // if the resolve() is an object of the Promise, and we need to resolve the result of the Promise, If (typeof value === 'object' &&typeof value.then === 'function') {if (typeof value === 'object' &&typeof value.then === 'function') {if (typeof value === 'object' &&typeof value.then === 'function') { Because the arguments in the then method, once the next Promise resolves, execute the arguments in the then method and pass in the corresponding values. // This._resove => obj.onFulfilled? .(this._value) // this._reject => obj.onRejected? .(this._value) value.then(this._resolve.bind(this), This._reject. Bind (this)) return} this._nextTick(() => {this._state = 'fulfilled' this This._queue.forEach((obj) => {// The try catch will be used to catch any errors that exist within the function. This._value is passed down as const val = obj.onFulfilled? obj.onFulfilled(this._value) : This is the value of this._value // reoslve, which will be the first parameter in the current Promise Then method: Promise.then((res) => {consolle.log(res)}) // console.resolve Promise obj.resolve(val)} catch (e) {obj.reject(e)}})})})} // reject _reject(error) {if (this._state ! == 'pending') return this._nextTick(() => { this._state = 'rejected' this._value = error this._queue.forEach((obj) => { try { const val = obj.onRejected ? obj.onRejected(this._value) : This._value // When the current reject is executed, a new Promise is returned. Obj. resolve(val)} catch (e) {obj.reject(e)}})})})})}

Declare Promise static methods

There are four static methods: Promise. Resolve, Promise. Reject, Promise. All, Promise. Race.

class Promise { ... /** * direct resolve */ static resolve(value) {return value} else if (value instanceof Promise) {return value} else if (value instanceof Promise) (typeof value === 'object' && typeof value.then == 'function') {const then = value.then return new Promise((resolve) => {then. Call (value, resolve)})} else { Return new Promise((resolve) => resolve(value))}} /** * reject promise.reject (); So just go back. */ static reject(value) { return new Promise((resolve, Reject) => reject(value)} /** * pass in an array of 'promises' and return a new instance of' promises', return the values in order on success, */ static all(promises) {return new Promise((resolve,); Reject) => {let count = 0 let arr = [] // Promise. Foreach ((Promise, reject) => {let count = 0 let arr = [] // Promise. Foreach ((Promise, reject) => {let count = 0 let arr = [] // Promise. Then ((res) => {count++ arr[index] = res if (count === === Promise.length) {resolve(arr)}}, err => reject(err)})})})} /** * pass 'Promise' in array form and return a new instance of 'Promise', */ static race(promises) {return new Promise((promises, reject) => {promises. Foreach ((promises, reject)) {promises. If ((res) = ref ((res); if (res) = ref (res); if (res) = ref (res); if (res) = ref (res); if (res) = ref (res); err => reject(err)) }) }) } ... }

Promise implements the full code

class Promise { _value _state = 'pending' _queue = [] constructor(fn) { if (typeof fn ! == 'function') { throw new Error('Promise resolver undefined is not a function') } /* new Promise((resolve, Reject) => {resolve: successful reject: }) */ fn(this._resolve. Bind (this), this._reject. Bind (this))}); * * @Param {*} OnRejected * @Return {*} * @Memberof Promise */ Then (Fulfilled for what it is, OnRejected) {// return new Promise((reject, reject) => {// Rejected) {// Rejected; This is very depressing because the Promise can be fulfilled ahead of time, and then it can be recorded behind the then method. If (this._state === 'Fulfilled' && Onfulfilled) { this._nextTick(onFulfilled.bind(this, this._value)) return } if (this._state === 'rejected' && onRejected) { this._nextTick(onRejected.bind(this, This._value)) return} // Add the arguments of the current Promise's then method to the new Promise's resolve, reject; This._queue.push({onFulfilled, onRejected, resolve, Reject {memberof member; reject; reject; reject; reject; reject; reject; reject; reject; reject this.then(null, ** @Param {*} onDone * @Return {*} * @MemberOf Promise */ finally(onDone) {return This. then((value) = BB0 {onDone() return value}, (value) => {onDone()) // /** ** Resolve ** @static * @Param {*} value * @return {*} * @memberof Promise */ static resolve(value) { if (value instanceof Promise) { return value } else if (typeof value === 'object' && typeof Then return new Promise((resolve) => {return new Promise((resolve)) =>; } return new Promise((Resolve) => Resolve (value))}} return new Promise((Resolve) => Resolve (value))}} return new Promise((Resolve) => Resolve (value)))}} * * @member * @param {*} value * @return {*} * @memberOf Promise */ static reject(value) {return new Promise(((resolve, reject) => reject(value))} /** * pass an array of 'promises' and return a new instance of' promises'. * * @static * @param {*} promises * @memberof Promise */ static all(promises) {return new. * * @memberof Promise */ promises */ static all(promises) {return new Promise((resolve, reject) => { let count = 0 let arr = [] if (Array.isArray(promises)) { if (promises.length === 0) { return resolve(promises) } promises.forEach((promise, Then ((res) => {count++ arr[index] = res if (count === === promises.length) { resolve(arr) } }, Err => reject(err))}) return} else {reject(' ${promises} is not Array ')})})} * * @static * @param {*} promises * @return {*} * @memberof Promise */ static race(promises) {return. * @memberof Promise */ static race(promises) {return. * * @memberof Promise */ static race(promises) {return new Promise((resolve, reject) => { if (Array.isArray(promises)) { promises.forEach((promise, Then ((res) => {return (res)}, Err => reject(err))} else {reject(' ${promises} is not Array ')}})} if (typeof) {err = reject(' ${promises} is not Array ')}}) MutationObserver ! == 'undefined') {// Browser // This can be used separately to avoid unnecessary overhead, as it would need to generate nodes each time. const observer = new MutationObserver(fn) let count = 1 const textNode = document.createTextNode(String(count)) observer.observe(textNode, { characterData: true }) textNode.data = String(++count) } else if (typeof process.nextTick ! == 'undefined') {// Node process.nextTick(fn)} else {setTimeout(fn, 0)}} If (this._state! == 'pending') return // The example above returns a Promise instead of a value, so we need to do a special handling here. // if the resolve() is an object of the Promise, and we need to resolve the result of the Promise, If (typeof value === 'object' &&typeof value.then === 'function') {if (typeof value === 'object' &&typeof value.then === 'function') {if (typeof value === 'object' &&typeof value.then === 'function') { Because the arguments in the then method, once the next Promise resolves, execute the arguments in the then method and pass in the corresponding values. // This._resove => obj.onFulfilled? .(this._value) // this._reject => obj.onRejected? .(this._value) value.then(this._resolve. Bind (this), this._reject. Bind (this)) return} If you resolve directly in the thread, the state and value appear to change directly, before the main process is completed and they will be changed during the execution of the microtask. // So moving state changes and value changes out of the microtask, This._state = 'fulfilled' this._value = value // Fulfill this._nextTick(() => {Fulfill this. This._queue.forEach((obj) => {// The try catch will be used to catch any errors that exist within the function. This._value is passed down as const val = obj.onFulfilled? obj.onFulfilled(this._value) : This is the value of this._value // reoslve, which will be the first parameter in the current Promise Then method: Promise.then((res) => {consolle.log(res)}) // console.resolve Promise obj.resolve(val)} catch (e) {obj.reject(e)}})})})} // reject _reject(error) {if (this._state ! == 'pending') return this._state = 'rejected' this._value = error this._nextTick(() => { this._queue.forEach((obj) => { If (const val = obj.onrejected (this._value)) {const val = obj.onrejected (this._value); // reject (this._value); Should be able to resolve normally, So let's just say resolve, Obj. reject(this._value)} else {obj.reject(this._value)}} catch (e) {obj. obj.reject(e) } }) }) } }

Full demo effect

Blog original address

Complete code for this project: GitHub

The above is the implementation of Promise. Of course, this is different from the full Promise /A+ specification. This is for study purposes only.

QQ group The public,
Front end miscellaneous group

Wax gourd random

I created a new group to learn from each other, no matter you are preparing to enter the pit, or halfway into the students, I hope we can share and communicate together.


QQ group: 810018802, click to join