preface

Recently, when I was asked about the solution of asynchronous programming, I found that my understanding was not clear and it was easy to be asked. This paper mainly records the whole process of realizing A promise, and we suggest that you read it together with the PROMISE A+ specification.

Promise? what & why

The Promise object is used to represent the final completion (or failure) of an asynchronous operation and its resulting value. The ES6 specification proposes that Promise objects can express asynchronous operations as a flow of synchronous operations, avoiding layers of nested callback functions. This allows asynchronous methods to return values as synchronous methods do: asynchronous methods do not immediately return the final value, but rather return a promise to give the value to the consumer at some point in the future (the better semantically aysnc/await is just syntactic sugar for promise and generator. Async-await is built on top of the promise mechanism.)

The basic structure of promises

The Promise constructor takes a function as an argument, which we’ll call excuter, which in turn takes resolve and reject, two functions. This function is executed when the promise is initialized,

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("resolve");
  }, 1000);
});
Copy the code

Promise base state and value

A Promise must be in one of the following states:

  1. Pending: The initial state, which can be settled in a depressing or rejected state.
  2. This is a big pity: which means that the operation is completed successfully and a private value value will be returned
  3. Rejected: Indicates that the operation failed and returns a private value, Reason

The state can only change from Pending to depressing or from Pending to Rejected. After the state changes, it will not change again and will always remain in this state.

const PENDING = "Pending"; // Wait for const FULLFILLED = "FULLFILLED "; // const REJECTED = "REJECTED "; Constructor (excuter) {constructor(excuter) {if (typeof excuter! == "function") { throw new Error("myPromise must accept a function"); } this.state = PENDING; this.reason = undefined; this.value = undefined; try { excuter(this._resolve.bind(this), this._reject.bind(this)); } catch (error) { this.reject(error); }} // The Promise class also has the static method resolve to avoid the same name _resolve(val) {if (this.state! == PENDING) return; this.state = FULLFILLED; this.value = val; } _reject(err) { if (this.state ! == PENDING) return; this.state = REJECTED; this.reason = err; }}Copy the code

The most important then method

Provide a THEN method to access the current or final value or Reason. The THEN method needs to satisfy:

  1. Accept two optional functions as arguments
  2. Ignored when arguments are not functions (2.2.1)
  3. Both functions execute asynchronously and queue events for the next tick
  4. When the onFulfilled function is called, the value of the current Promise will be passed in as a parameter.
  5. When the onRejected function is called, the reason for the failure of the current Promise’s reason is passed in as an argument.
  6. OnFulfilled and onRejected cannot be called more than once, and they cannot be called before the state changes
  7. The then function returns a Promise, which is why THEN can be called chained
  8. Then can be called multiple times by the same Promise

According to the Promise A + specification: The THEN method can be called multiple times by the same Promise object

  1. When the promise succeeds, all ondepressing needs to be called back in sequence according to its registration order
  2. When the Promise fails, all onRejected are called back in the order in which they are registered

With these rules in mind, we can improve our myPromise by first supporting multiple calls, which can be maintained with two arrays onFullfilledQueues and onRejectedQueues, by adding callbacks to queues when the THEN method is registered and waiting for the state change to execute.

constructor(excuter) { if (typeof excuter ! == "function") { throw new Error("myPromise must accept a function"); } this.state = PENDING; this.reason = undefined; this.value = undefined; This. onFullfilledQueues = []; // Add the failed callback function this.onRejectedQueues = []; try { excuter(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error); }}Copy the code

Implement the THEN method. After the state of a Promise is fulfilled, its callback function will be executed, and the result returned by the callback function will be regarded as value and returned to the next Promise(that is, the Promise generated in THE THEN). The state of the next Promise is also changed (resolve or Reject), its callback is executed, and so on… See the Promise Solution (2.3) for a solution:

ResolvePromise (Promise, x, resolve, Reject) {// 2.3.1 if (promise === x) {return Reject (new TypeError("error found in: promise A+ 2.3.1")); } //2.3.2 if (x instanceof myPromise) {if (x.state === FULLFILLED) {resolve(x.value); } else if (x.state === REJECTED) { reject(x.reason); } else { x.then((y) => { this.resolvePromise(promise, y, resolve, reject); }, reject); }} else if (//2.3.3 x! == null && (typeof x === "object" || typeof x === "function") ) { var executed; try { var then = x.then; if (typeof then === "function") { then.call( x, (y) => { if (executed) return; executed = true; this.resolvePromise(promise, y, resolve, reject); }, (e) => { if (executed) return; executed = true; reject(e); }); } else { resolve(x); } } catch (e) { if (executed) return; executed = true; reject(e); } } else { resolve(x); }}Copy the code

According to the rules for the THEN method above, we know that the state of the returned new Promise object depends on the execution of the current THEN method callback and the return value. Here are a few scenarios:

  1. If onFulfilled or onRejected returns a value x,
    • If x is not a Promise, make x directly the value of the newly returned Promise object — see point 4
    • If x is a Promise, the latter callback will wait for the state of the Promise object (x) to change before it is called, and the new Promise state is the same as the state of X.
Promise1 = new promise ((resolve, reject) => {setTimeout(() => {resolve()}, 1000)}) promise2 = promise1. Then (res => {console.log(res) // Prints after 1 second: Promise let promise1 = new promise ((resolve, reject) => {setTimeout(() => {resolve()}, // return new Promise((resolve, resolve, Reject) => {setTimeout(() => {resolve(' here returns a Promise')}, 2000)})}) promise2. Then (res => {console.log(res) // printout after 3 seconds: return a Promise})Copy the code
  1. If onFulfilled or onRejected throws an exception (e), then promise2 must become failure and return the failure value (e)
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, // return new Promise((resolve, resolve, Reject) => {setTimeout(() => {resolve(' here returns a Promise')}, 2000)})}) promise2. Then (res => {console.log(res) // printout after 3 seconds: return a Promise})Copy the code
  1. If ondepressing is not a function and the state of promise1 is Fulfilled, promise2 must become Fulfilled and return the value of promise1’s success — the second stipulation is that this parameter will be ignored when it is not a function
  2. If onRejected is not a function and the status of promise1 is failed, promise2 must change to failed and return the value of promise1 failure

Refine the THEN method according to the above rules and the solution process

// Then (onFullfilled, onRejected) {onFullfilled = typeof onFullfilled === "function"? onFullfilled : function (x) { return x; }; onRejected = typeof onRejected === "function" ? onRejected : function (e) { throw e; }; Let Promise = new myPromise((resolve, reject) => {switch (this.state) {case PENDING: this.onFullfilledQueues.push(() => { try { var x = onFullfilled(this.value); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); }}); this.onRejectedQueues.push(() => { try { var x = onRejected(this.reason); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); }}); break; case FULLFILLED: try { var x = onFullfilled(this.value); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); } break; case REJECTED: try { var x = onRejected(this.reason); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); } break; }}); return promise; }Copy the code

Analyze the code execution:

  1. The procedure instance that instantiates promsie executes the function passed in, binding resolve and reject inside the class to the parameter
  2. Encountering the THEN method stores the parameters to the execution queue of the instance (the previous callback function). The THEN method returns a new Promise, but the callback registered in the THEN method still belongs to the previous Promise, and the THEN method can be called multiple times by the same Promise
  3. To be clear: a promise’s state is only changed by its internal resolve Reject
  4. The key in the Promise is to ensure that the onFulfilled and onRejected parameters passed in by the THEN method must be executed in the new implementation stack after the event loop in which the THEN method is called
  5. The function in the queue is executed when the promise state changes We then determine the state of the next promise based on the current state (then returns that). We pass the then method parameters (resolve,reject) that return the new promise to the resolvePromise, so that we can change the state of the new promise. That’s how chain calls work.

6. Resolve: A special determination is made to determine whether the value of resolve is a Promise instance. If it is a Promise instance, Then the state change interface of the current Promise instance will be re-registered into the ondepressing of the Promise corresponding to the resolve value, that is, the state of the current Promise instance will depend on the state of the Promise instance of the resolve value.

Catch method

This is essentially a then method that only deals with error states

Catch (onRejected) {return this. Then (undefined, onRejected)}Copy the code

Promises tests

Usage:

  1. git clone
  2. Export the interface to the test in the file
  3. Modify the test command to test its promise.js file in package.json (depending on its own file)"test": "promises-aplus-tests promise.js

It takes a while to run through the test. The tool case does not test promsie passed to resolve(), which I did in the child Obar’s1. Handwritten Promise bucketsWe have discussed this issue, and the quality of the articles of the leaders is very high. We can follow them. Teleport:Big lot

The complete code

const PENDING = "Pending"; // Wait for const FULLFILLED = "FULLFILLED "; // const REJECTED = "REJECTED "; Constructor (excuter) {constructor(excuter) {if (typeof excuter! == "function") { throw new Error("myPromise must accept a function"); } this.state = PENDING; this.reason = undefined; this.value = undefined; This. onFullfilledQueues = []; // Add the failed callback function this.onRejectedQueues = []; try { excuter(this._resolve.bind(this), this._reject.bind(this)); } catch (error) { this._reject(error); }} // The function in the array needs to be called asynchronously, using setTimeout to simulate asynchracy. _resolve(val) { // console.log(val); If (val instanceof myPromise) {return val.then(this._resolve. Bind (this), this._reject. Bind (this)); // return val.then(this._resolve, this._reject); } const run = () => { if (this.state ! == PENDING) return; this.state = FULLFILLED; this.value = val; // Execute the functions in the success queue, and empty the queue let cb; while ((cb = this.onFullfilledQueues.shift())) { cb(this.value); }}; // To support synchronous promises, call setTimeout(run) asynchronously; } _reject(err) { const run = () => { if (this.state ! == PENDING) return; this.state = REJECTED; this.reason = err; // Execute the functions in the failed queue in sequence and empty the queue let cb; while ((cb = this.onRejectedQueues.shift())) { cb(err); }}; setTimeout(run); ResolvePromise (Promise, x, resolve, reject) {// console.log(Promise, x); Return Reject (New TypeError("error found in: Promise A+ 2.3.1")); if (promise === x) {return Reject (new TypeError("error found in: Promise A+ 2.3.1")); } if (x instanceof myPromise) { if (x.state === FULLFILLED) { resolve(x.value); } else if (x.state === REJECTED) { reject(x.reason); } else { x.then((y) => { this.resolvePromise(promise, y, resolve, reject); }, reject); } } else if ( x ! == null && (typeof x === "object" || typeof x === "function") ) { var executed; try { var then = x.then; if (typeof then === "function") { then.call( x, (y) => { if (executed) return; executed = true; this.resolvePromise(promise, y, resolve, reject); }, (e) => { if (executed) return; executed = true; reject(e); }); } else { resolve(x); } } catch (e) { if (executed) return; executed = true; reject(e); } } else { resolve(x); } // then(onFullfilled, onRejected) {onFullfilled = typeof onFullfilled === "function"? onFullfilled : function (x) { return x; }; onRejected = typeof onRejected === "function" ? onRejected : function (e) { throw e; }; Let Promise = new myPromise((resolve, reject) => {switch (this.state) {case PENDING: this.onFullfilledQueues.push(() => { try { var x = onFullfilled(this.value); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); }}); this.onRejectedQueues.push(() => { try { var x = onRejected(this.reason); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); }}); break; case FULLFILLED: try { var x = onFullfilled(this.value); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); } break; case REJECTED: try { var x = onRejected(this.reason); this.resolvePromise(promise, x, resolve, reject); } catch (error) { reject(error); } break; }}); return promise; } // -----Promise class static method ----- static resolve(value) {if (value instanceof myPromise) {return value; } return new myPromise((resolve, reject) => { resolve(value); }); } static reject(reason) { return new myPromise((resolve, reject) => { reject(reason); }); } static all(list) { return new myPromise((resolve, reject) => { let results = []; let count = 0; for (let index = 0; index < list.length; index++) { const p = list[index]; this.resolve(p).then( (res) => { results[index] = res count++; if (count == list.length) resolve(results); }, (err) => { reject(err); }); }}); } static race(list) {return new myPromise((resolve, reject) => {for (let p of list) { This. Resolve (p). Then ((res) => {resolve(res); }, (err) => { reject(err); }); }}); } // implement catch catch(onRejected) {return this. Then (onRejected); } finally finally(onDone) {return this.then(onDone, onDone); }}Copy the code

Afterword.

If there is any doubt or wrong place in the code, you are welcome to correct it and make progress together. Ask for “like” three times QAQ

Reference links:

  • Promises/A+ Promises
  • Understand the then in Promise in a simple way
  • Promise Implementation Principle (I) — Basic implementation
  • This time, understand the Promise principle once and for all