Promise is an asynchronous solution. Its emergence solves the problem of using callback function to process asynchronous logic and cause callback hell. This paper mainly analyzes Promise from the basic usage of Promise, Promise A+ and handwritten Promise.

I Promise

The meaning of the Promise

Promise objects have two states:

  1. The state of an object is unaffected by external influences. Promise has three states:This is a big pity (which is pending), which must be fulfilled (fulfilled) and rejected (failed).Only the result of an asynchronous operation can determine which state it is in. No other operation can change this state.
  2. Once the state changes, it doesn’t change again, and you can get this at any other time. A Promise’s state can change in only two ways:pending -> fulfilledorpending -> rejected

Promise method

  1. Promise is a constructor that needs to be usednewKeyword generation instance;
  2. The Promise has an executor that executes the function immediately, each time it is new (more on that later in handwriting);
  3. Executor takes two parametersresolveandrejectMethod: resolve indicates a successful callback, reject indicates a failed callback.
  4. Promise.prototype.then() : The first argument of the then method is passed in after the resolve and reject methodsonFulfilledRepresents the callback to resolve on success, the second argument to the then methodonRejectedRepresents a callback when reject fails. Both callback functions are optional;
  5. Promise.prototype. Catch () : null then(rejection) or.then(undefined, rejection)
  6. Promise. Prototype. Finally () method: whatever Promise the object’s state, will eventually perform this method;
  7. Promise.all() : used to wrap multiple Promise instances into new Promise instances, such as const p = promise.all ([p1, p2, p3]); This is a big pity. Only when the states of P1, P2 and P3 are programmed with depressing, can the P state be programmed with depressing. At this time, the results returned by P1, P2 and P3 form an array and are passed to the callback function of P. If one of the states is Rejected, the rejected instance is returned.
  8. Promise.race() method: Like promise.all () method, multiple Promise instances are assembled into a new Promise instance, but as long as the state of any one of them changes, the return value of the first changed Promise instance is the returned callback function;
  9. Promise.allsettled () method: Accept a group of Promise instances as parameters and package them into a new Promise instance. The package will end only after all parameters return the result, no matter the state is pity or Rejected.
  10. Promise. Any () method: Receive a group of Promise instances as parameters and package them into a new Promise instance. As long as the state of one instance becomes a big pity, the packaging instance will become a big pity; If all parameter instances are Rejected, the status of the wrapper function changes to Rejected.
  11. Promise.reslove() method: a. If the argument is an instance of a Promise, the Promise is returned unchanged; B. If the parameter isthenableObject, that is, havethenMethod object, which then becomes a Promise object and executes the then method immediately; C. If there is no then method or no object, the promise.resolve () method returns a new Promise object and returns the argument to the callback; D. If no parameters, return an Resolved Promise object.
  12. Promise.reject() : returns a new Promise object in the rejected state;

The above only briefly introduces the methods that exist in the Promise, and there is no specific example to introduce how each method is used. If you do not understand, you can see the Promise of Teacher Ruan Yifeng. When I first learned about promises, I had a lot of questions: How does the underlying Promise implement asynchronous operations? The promise.then () method is a microtask. How is this implemented? For those of you who don’t know eventLoop, check it out online and there are plenty of articles about it.

Second, the Promise A +

Before writing Promise, let’s talk about what is Promise A+. The underlying implementation specification of Promise relies on Promise A+ to realize.


A promise represents the end result of an asynchronous operation. The primary way to interact with a promise is through its then method, which registers callbacks to receive the final value of a promise or the reason why a promise cannot be fulfilled.

The specification details the behavior of the THEN method and provides an interoperable foundation on which all Promises/A + compatible Promise implementations can rely. Therefore, the specification should be considered very stable. Although Promises/A + organizations will sometimes revise this specification with minor backward compatible changes to address newly discovered extremes, we will only integrate large or backward incompatible changes after careful consideration, discussion, and testing.

Historically, Promises/A + clarifies the act clause of early Promises/A proposals by expanding it to cover actual acts and omits unspecified or problematic parts.

Finally, the core Promises/A + specification doesn’t deal with how to make, keep, or reject Promises. Instead, it focuses on providing an interoperable then approach. Future work in accompanying specifications may address these topics.

1. The term

1.1. There is a then method in “Promise”, which is a function or object that conforms to this canonical behavior; 1.2. “thenable” defines the objects and functions of the THEN method 1.3. “value” represents any valid JavaScript value (including undefined, Ableable or promise) 1.4. “Exception” represents the value thrown by the throw 1.5. “Reason” represents the reason why the promise was rejected

2. Necessary conditions

2.1 Promise state

Promise must be one of these three states: Pending, fulfilled and rejected. This is a big pity.

2.1.1. When a Promise is pending:

2.1.1.1. It may become a pity state or the Rejected state.

2.1.2. When a promise is fulfilled:

2.1.2.1. There must be no transition to any other state

2.1.2.2. There must be a value that cannot be changed

2.1.3 when a promise is in the Rejected state:

2.1.3.1. There must be no transition to any other state

2.1.3.2. There must be a cause that cannot be changed

Here, “must not be changed” implies an immutable identity (e.g. ===), but it does not imply immutability of depth.

2.2 then method

A Promise must provide a THEN method to access the final value or cause.

Promise’s then method accepts two arguments:

promise.then(onFulfilled, onRejected);
Copy the code

2.2.1 onFulfilled and onRejected are both optional parameters

2.2.1.1. If ondepressing is not a function, it must be ignored

2.2.1.2. If onRejected is not a function, it must be ignored

2.2.2. If Ondepressing is a function

2.2.2.1. It must be called after the promise state is fulfilled, and the promise value is its first parameter.

2.2.2.2. It must not be called before the state of the promise becomes a pity.

2.2.2.3. It must not be called more than once.

2.2.3. If onRejected is a function

2.2.3.1. It must be called after the Promise changes to Rejected, with reason, the promise result, as its first argument.

2.2.3.2. It must not be called before the Promise state changes to Rejected.

2.2.4. OnFulfilled or onRejected must not be called before only the platform code is included in the implementation context stack [3.1]

OnFulfilled and onRejected must be called as a function (without this value) [3.2]

2.2.6. The THEN method can be called multiple times on the same promise

2.2.6.1. If a promise is a big pity, all corresponding onFulfilled callbacks must be executed in the order in which they originally called THEN

2.2.6.2. If the promise is Rejected, all corresponding onRejected callbacks must be executed in the order in which they originally called THEN

2.2.7. Then must return a Promise [3.3]

promise2 = promise1.then(onFulfilled, onRejected);
Copy the code

2.2.7.1. If ondepressing or onRjected returns a value x, run the Promise settlement [Resolve]

2.2.7.2. If onFulfilled or onRejected throws an exception E, promise2 must use E as the reason to be rejected

2.2.7.3. If ondepressing is not a function and promise1 is resolved, promise2 must be resolved with the same value as promise1

2.2.7.4. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1

2.3 Promise Solution

The Promise resolver is an abstract operation that takes as input a promise and a value, which we represent as [[Resolve]](Promise, x). If X is a Thenable, it tries to make the Promise adopt x’s state and assumes that X behaves at least somewhat like promise. Otherwise, it will resolve the promise with the value x.

This thenable feature makes the Promise implementation more generic: as long as it exposes A THEN method that follows the Promise/A+ protocol. This also allows implementations that follow the Promise/A+ specification to coexist well with less formal but usable implementations.

To run [[Resolve]](promise, x), perform the following steps:

2.3.1. If a Promise and X reference the same object, reject the Promise with a TypeError as the reason

2.3.2. If X is a Promise, adopt its state: [3.4]

If X is pending, the Promise must remain in the pending state until X becomes a pity or rejected state 2.3.2.2. This is very depressing. If X is fulfilled, the promise 2.3.2.3 will be solved with the same value. If x is Rejected, use the same reason to reject the promise

Otherwise, if x is an object or function

2.3.3.1. Make THEN x.teng. [3.5]

2.3.3.2. If retrieving the attribute x.teng causes an exception E to be thrown, reject the promise with e as the reason

2.3.3.3. If then is a function, call it with x as this. The then method takes two callbacks, the first called resolvePromise and the second called rejectPromise:

2.3.3.3.1. If resolvePromise is called with a value y, run [[Resolve]](promise, y).

2.3.3.3.2. If rejectPromise is invoked with a reason r, reject the promise with r.

2.3.3.3.3. If both resolvePromise and rejectPromise are called, or if multiple calls are made to the same parameter, the first call takes precedence and subsequent calls are ignored.

2.3.3.3.4. If calling THEN throws an exception e

2.3.3.4.1. If resolvePromise or rejectPromise has already been invoked, ignore it

2.3.3.4.2. Otherwise, use E as the reason to reject promise

2.3.3.4. If then is not a function, solve the promise with x

2.3.4. If x is not an object or function, use x to resolve the promise

If a promise is resolved with a cyclic thenable chain, the recursive nature of [[Resolve]](promise, thenalbe) will eventually cause [[Resolve]](promise, thenable) to be called again, Following the algorithm above will result in infinite recursion. The specification does not mandate handling such cases, but it does encourage implementors to detect the presence of such recursions and reject promises with an informative TypeError as a reason. [3.6]

annotations

3.1. Here “platform code” means the engine, environment, and promise implementation code. In practice, this needs to ensure that the onFulfilled and onRejected are executed asynchronously and should be executed with the new execution stack after the event loop in which the THEN method is called. This can be done with a “macro task” mechanism such as setTimeout or setImmediate, or with a “microtask” mechanism such as MutationObserver or process.nexttick. Because the promise implementation is considered “platform code,” it may already contain a task scheduling queue when its own handler is invoked.

3.2. In strict mode, this will be undefined; In non-strict mode, this will be a global object.

3.3. Allow promisE2 === PROMISE1 if all requirements are implemented. Each implementation should document whether or not promisE2 === promise1 is produced and under what circumstances promisE2 === PROMISe1 occurs.

3.4. In general, x is not known to be a real promise until it comes from the current implementation. This rule allows those special implementations to adopt a state of Promise that meets known requirements.

3.5. The program stores a reference to x. teng, tests that reference, and then calls that reference, avoiding multiple access to the x. teng property. Such precautions are important to ensure the consistency of visitor attributes, which can change in value between searches.

3.6. The implementation should not impose arbitrary limits on the depth of the Thenable chain and assume that beyond that arbitrary limit it will recurse indefinitely. Only real loops should raise a TypeError; If an infinite loop is encountered with Thenable, always performing recursion is the correct behavior.


Three, simulation Promise implementation

Promise.then() methods are asynchronous tasks, and as mentioned in Promise A+, can be implemented using “macro tasks” mechanisms like setTimeout or setImmediate, It can also be implemented using a “microtask” mechanism such as MutationObserver or process.nexttick. The Promise implementation is based on the V8 engine, promise.then() is a microtask, so the following simulation code creates a microtask using queueMicrotask().

Here’s an example:

// Execute the function immediately
function executor(resolve, reject) {
  // Successful callback
  resolve("success");
  // Failed callback
  reject("error");
}
const promise = new Promise(executor);

promise.then(
  / / onFulfilled function
  (value) = > console.log(1, value), // 1 success
  / / onRejected function
  (reason) = > console.log(2, reason)
);

// Output 1 success
Copy the code

According to the results of the example and the rule analysis of Promise A+, the following results are obtained:

  1. Promise has three states:This is a big pity (which is pending), which must be fulfilled (fulfilled) and rejected (failed).;
  2. A Promise’s state can change in only two ways:pending -> fulfilledorpending -> rejected;
  3. Promise has an executor function that executes immediately, and two callbacks, resolve and Reject, that change the state.
  4. The then method is used to access the final successful value or the cause of the failure, if the state is successful call successful callback, if it is failure call failed callback;

1. Create MyPromise classes and resolve and Reject methods

class MyPromise {
  constructor(executor) {
    // Execute the function immediately
    executor();
  }

  /** the arrow function is used for this reason: 2. The new operator creates an empty JavaScript object as the context for this. 3. The arrow function does not create an execution context for this, which is an instance of MyPromise */
  // Change the status after success
  resolve = () = > {};
  // Change the failed state
  reject = () = > {};
}
Copy the code

2. MyPromise state management

const PEDDING = "pedding";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  // Initialize the storage state. The default value is pending
  status = PEDDING;
  // Initialize the resolve parameter value
  value = null;
  // Initialize reject parameter reason
  reason = null;

  // constructor
  constructor(executor) {
    // Execute the function immediately
    executor(this.resolve, this.reject);
  }

  /** the arrow function is used for this reason: 2. The new operator creates an empty JavaScript object as the context for this. 3. The arrow function does not create an execution context for this, which is an instance of MyPromise */
  // Change the status after success
  resolve = (value) = > {
    // If the state is pending, set the state to depressing and cache the value
    if (this.status === PEDDING) {
      // This is a big pity
      this.status = FULFILLED;
      // Cache successful values
      this.value = value; }};// Change the failed state
  reject = (reason) = > {
    // If the state is pending, set it to Rejected and cache the failure cause
    if (this.status === PEDDING) {
      // Change the state to Rejected
      this.status = REJECTED;
      // Cache failure cause
      this.reason = reason; }}; }Copy the code

3. Then method implementation

// ...
class MyPromise {
  / /...

  / / then method
  then(onFulfilled, onRejected) {
    // If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
    if (this.status === FULFILLED) {
      // Call the success callback to return the success value
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // Call the failure callback to return the cause of failure
      onRejected(this.reason); }}}Copy the code

Test it out:

const promise = new MyPromise((resolve, reject) = > {
  resolve("success");
  reject("error");
});

promise.then(
  (value) = > console.log("resolve", value),
  (reason) = > console.log("reject", reason)
);

// Output: resolve success
Copy the code

That’s right. Go ahead.

4. The asynchronous logic and then methods in the Promise are called multiple times

The above implementation does not have asynchronous logic handling. What happens if asynchronous logic is added?

const promise = new MyPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve("success");
    reject("error");
  });
});

promise.then(
  (value) = > console.log("resolve", value),
  (reason) = > console.log("reject", reason)
);

// Output: nothing!
Copy the code

Why does this happen? As mentioned earlier in eventLoop, js creates execution contexts (global, function, and Eval execution contexts) at compile time. Execution contexts are stored in the execution context stack (execution stack). Es6 lets and const block-level scopes are stored in the lexical environment. While asynchronous tasks are stored in the task queue, setTimeout is used here. It is a macro task that needs to wait for the completion of the task in the call stack before executing the code in the macro task.

Analyze the above code:

The main thread executes the code immediately, setTimeout is the macro task, which is stored in the macro task queue, then method executes immediately in the execution stack, and status is pending, which terminates. If resolve is removed from the macro task, there is no code behind it, so it will not return.

So how to solve it? The data can be cached using a queue (first-in, first-out) data structure. When the THEN method is executed, the pending callback is cached, and when the resolve or Reject method is called, the callback cache is determined to see if there is a callback cache in the cache, and if so, it is executed. As follows:

Two new arrays are added to MyPromise
// The callback cache was successfully initialized
onFulfilledCallback = [];
// Failed to initialize the callback cache
onRejectedCallback = [];
Copy the code
The then method callback is stored in an array
/ / then method
then(onFulfilled, onRejected) {
  // If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
  if (this.status === FULFILLED) {
    // Call the success callback to return the success value
    onFulfilled(this.value);
  } else if (this.status === REJECTED) {
    // Call the failure callback to return the cause of failure
    onRejected(this.reason);
  } else {
    // Cache the current success callback and queue it to success
    this.onFulfilledCallback.push(onFulfilled);
    // Cache the current failed callback and queue it to failure
    this.onRejectedCallback.push(onRejected); }};Copy the code
Resolve and Reject perform cached callbacks
// Change the status after success
resolve = (value) = > {
  // If the state is pending, set the state to depressing and cache the value
  if (this.status === PEDDING) {
    // This is a big pity
    this.status = FULFILLED;
    // Cache successful values
    this.value = value;
    // If a value exists in the cache callback, execute
    while (this.onFulfilledCallback.length) {
      // The callback is queued
      this.onFulfilledCallback.shift()(value); }}};// Change the failed state
reject = (reason) = > {
  // If the state is pending, set it to Rejected and cache the failure cause
  if (this.status === PEDDING) {
    // Change the state to Rejected
    this.status = REJECTED;
    // Cache failure cause
    this.reason = reason;
    // If a value exists in the cache callback, execute
    while (this.onRejectedCallback.length) {
      // The callback is queued
      this.onRejectedCallback.shift()(reason); }}};Copy the code

Write an example to execute the above program as follows:

const promise = new MyPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve("success");
    reject("error");
  });
});

promise.then(
  (value) = > console.log(1."resolve"),
  (reason) = > console.log(2."reject")); promise.then((value) = > console.log(3."resolve"),
  (reason) = > console.log(4."reject"));/ / output:
// 1 resolve
// 2 resolve
Copy the code

Is the expected result, so far we have solved the asynchronous invocation problem, then method multiple invocation problem.

5. Then chain call

The then method chaining calls return a Promise. The previous THEN returns a Promise object, and the next THEN takes the previous resolve argument.

Here’s an example:

 const promise = new MyPromise((resolve, reject) = > {
        resolve();
      });

      promise
        .then(() = > {
          console.log(1."resolve");
          return new MyPromise((resolve) = > {
            resolve("success");
          });
        })
        .then((value) = > console.log(2, value));

      // output: 1 "resolve"
      // test copy.html:99 Uncaught TypeError: Cannot read property 'then' of undefined
Copy the code

The error occurs because the then method does not return A value. In Promise A+, Promise’s then method is required to return A Promise object. As follows:

      // ...
      class MyPromise {
        / /...

        / / then method
        then(onFulfilled, onRejected) {
          const promise2 = new Promise((resolve, reject) = > {
            // If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
            if (this.status === FULFILLED) {
              // Call the success callback to return the success value
              const x = onFulfilled(this.value);
              this.resolvePromise(x, resolve, reject);
            } else if (this.status === REJECTED) {
              // Call the failure callback to return the cause of failure
              const x = onRejected(this.reason);
              this.resolvePromise(x, resolve, reject);
            } else {
              // Cache the current success callback and queue it to success
              this.onFulfilledCallback.push(onFulfilled);
              // Cache the current failed callback and queue it to failure
              this.onRejectedCallback.push(onRejected); }});return promise2;
        };

        resolvePromise = (x, resolve, reject) = > {
          if (x instanceof MyPromise) {
            x.then(resolve, reject);
          } else{ resolve(x); }}; }Copy the code

Execute again:

const promise = new MyPromise((resolve, reject) = > {
  resolve();
});

promise
  .then(() = > {
    console.log(1."resolve");
    return new MyPromise((resolve) = > {
      resolve("success");
    });
  })
  .then((value) = > console.log(2, value));

/ / output
// 1 "resolve"
// 2 "success"
Copy the code

Now that we’ve implemented the chain call, what happens if the then method returns itself? Let’s see what happens if the Promise itself calls itself? Throws an error as follows:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
Copy the code

Take a look at the case where the code encapsulated above executes itself and returns itself as follows:

const promise = new MyPromise((resolve, reject) = > {
  resolve();
});

const p1 = promise.then(() = > {
  return p1;
});

// Output test copy.html:108 Uncaught (in promise) ReferenceError: Cannot access 'P1' before initialization
Copy the code

If then is returned, throw an error.

      // ...
      class MyPromise {
        / /...

        / / then method
        then(onFulfilled, onRejected) {
          const promise2 = new Promise((resolve, reject) = > {
            // If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
            if (this.status === FULFILLED) {
              // Call the success callback to return the success value
              const x = onFulfilled(this.value);
              this.resolvePromise(promise2, x, resolve, reject);
            } else if (this.status === REJECTED) {
              // Call the failure callback to return the cause of failure
              const x = onRejected(this.reason);
              this.resolvePromise(promise2, x, resolve, reject);
            } else {
              // Cache the current success callback and queue it to success
              this.onFulfilledCallback.push(onFulfilled);
              // Cache the current failed callback and queue it to failure
              this.onRejectedCallback.push(onRejected); }});return promise2;
        };

        resolvePromise = (promise2, x, resolve, reject) = > {
          if (promise2 === x) {
            return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
          }
          if (x instanceof MyPromise) {
            x.then(resolve, reject);
          } else{ resolve(x); }}; }Copy the code

In execution let’s see

Uncaught (in promise) ReferenceError: Cannot access 'p1' before initialization
Copy the code

> < p class = “p0” > < P class = “p0” > < P class = “p0” > Based on eventLoop’s knowledge, you need to create an asynchronous task to wait for promisE2 to execute, using the queueMircotask method previously used.

then(onFulfilled, onRejected) {
  const promise2 = new Promise((resolve, reject) = > {
    // If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
    if (this.status === FULFILLED) {
      // === New part ===
      queueMicrotask(() = > {
        // Call the success callback to return the success value
        const x = onFulfilled(this.value);
        this.resolvePromise(promise2, x, resolve, reject);
      });
    } else if (this.status === REJECTED) {
      // Call the failure callback to return the cause of failure
      const x = onRejected(this.reason);
      this.resolvePromise(promise2, x, resolve, reject);
    } else {
      // Cache the current success callback and queue it to success
      this.onFulfilledCallback.push(onFulfilled);
      // Cache the current failed callback and queue it to failure
      this.onRejectedCallback.push(onRejected); }});return promise2;
};
Copy the code

The result of the run, and the type error we expect to throw, is as follows:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
Copy the code

6. Catch errors

Errors need to be caught at the executor stage of the code, and at the success and failure stages of the then method.

Catch actuator errors
        constructor(executor) {
          try {
            // Execute the function immediately
            executor(this.resolve, this.reject);
          } catch (err) {
            this.reject(err); }}Copy the code

Let’s take an example and see what happens

const promise = new MyPromise((resolve, reject) = > {
  throw Error("ececutor error!");
  resolve();
});

const p1 = promise.then(
  (value) = > console.log("resolve", value),
  (reason) = > console.log("reject", reason.message)
);

Reject ececutor error!
Copy the code
Error capture and code improvement in the THEN method
then = (onFulfilled, onRejected) = > {
  const promise2 = new Promise((resolve, reject) = > {
    const fulfilledMircotask = () = >
      queueMicrotask(() = > {
        try {
          // Call the success callback to return the success value
          const x = onFulfilled(this.value);
          this.resolvePromise(promise2, x, resolve, reject);
        } catch(error) { reject(error); }});const rejectedMircotask = () = >
      queueMicrotask(() = > {
        try {
          // Call the failure callback to return the cause of failure
          const x = onRejected(this.reason);
          this.resolvePromise(promise2, x, resolve, reject);
        } catch(error) { reject(error); }});// If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
    if (this.status === FULFILLED) {
      fulfilledMircotask();
    } else if (this.status === REJECTED) {
      rejectedMircotask();
    } else {
      // Cache the current success callback and queue it to success
      this.onFulfilledCallback.push(fulfilledMircotask);
      // Cache the current failed callback and queue it to failure
      this.onRejectedCallback.push(rejectedMircotask); }});return promise2;
};
Copy the code

Let’s take an example and see what happens

const promise = new MyPromise((resolve, reject) = > {
  resolve("success");
});

const p1 = promise
  .then((value) = > {
    console.log("resolve", value);
    throw Error("then throw error!");
  })
  .then(
    () = > console.log("resolve"),
    (reason) = > {
      console.log("reject", reason.message); });/ / output
// resolve success
// reject then throw error!
Copy the code

Error message in THEN was successfully printed.

7. Parameters in then are optional parameters

When implementing the THEN method, the onFulfilled and onRejected functions are passed in by default, which can also be optional.

then(onFulfilled, onRejected) {
  // If it is not a function, it is converted to a function
  const onFulfilledReal =
    Object.prototype.toString.call(onFulfilled) === "[object Function]"
      ? onFulfilled
      : (value) = > value;
  const onRejectedReal =
    Object.prototype.toString.call(onRejected) === "[object Function]"
      ? onRejected
      : (reason) = > {
          throw reason;
        };
  const promise2 = new Promise((resolve, reject) = > {
  // ...
  }
  return promise2;
}
Copy the code

Here’s an example to test:

const promise = new MyPromise((resolve, reject) = > {
  resolve("success");
});

const p1 = promise
  .then()
  .then()
  .then(
    (value) = > console.log("resolve", value),
    (reason) = > console.log("reject", reason)
  );

// Output resolve success
Copy the code

Expected results.

8. Improve the promiseResolve method

The code for promiseResolve in the evening according to the Promise A+ specification after 2.3.3

  1. X is a function or object, then = x.teng;
  2. If x. teng fails to execute, a reject(error) exception is thrown.
  3. If then is a function, call it with x as this and take two arguments: Then. Call (x, (y) => resolvePromise(promise, y, resolve, reject), (r) => reject(r));
  4. Otherwise, throw an error according to Promise A+

The code is as follows:

resolvePromise = (promise, x, resolve, reject) = > {
  // If promise and X refer to the same object, i.e. call itself, then a type error is thrown
  if (promise === x) {
    return reject(
      new TypeError("The promise and the return value are the same")); }if (typeof x === "object" || typeof x === "function") {
    let then;
    try {
      // assign x. teng to then
      then = x.then;
    } catch (error) {
      // If an error e is thrown when taking the value x. teng, reject a promise based on e
      return reject(error);
    }

    // If then is a function
    if (typeof then === "function") {
      let called = false;
      // call x as the function's scope this
      // Pass two callback functions as arguments, resolvePromise and rejectPromise
      try {
        then.call(
          x,
          // If resolvePromise is called with the value y, run [[Resolve]](promise, y)
          (y) = > {
            // If both resolvePromise and rejectPromise are invoked,
            // Or if the same argument is called more than once, the first call takes precedence and the rest are ignored
            // Implement this by adding a variable called before it
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          // If rejectPromise is invoked with argument r, reject the promise with argument r
          (r) = > {
            if (called) return;
            called = true; reject(r); }); }catch (error) {
        // If calling the then method throws an exception e:
        // If resolvePromise or rejectPromise has already been invoked, ignore it
        if (called) return;

        // Otherwise use e as the basis for rejecting the promisereject(error); }}else {
      // If then is not a function, execute a promise with an x argumentresolve(x); }}else {
    // If x is not an object or function, execute a promise with x as an argumentresolve(x); }};Copy the code

Resolve and reject static methods

// resolve static method
static resolve(value) {
  // If it is an instance of the current object, return as-is
  if (value instanceof MyPromise) {
    return value;
  }

  // Re-create a Promise object
  return new MyPromise((resolve) = > {
    resolve(value);
  });
}

// reject static methods
static reject(reason) {
  // If it is an instance of the current object, return as-is
  if (reason instanceof MyPromise) {
    return reason;
  }

  // Re-create a Promise object
  return new MyPromise((_, reject) = > {
    reject(reason);
  });
}
Copy the code

Test it out:

MyPromise.resolve("success").then((value) = > {
  console.log("resolve", value); // resolve success
});

MyPromise.reject("error").then(
  (value) = > {
    console.log("resolve", value);
  },
  (reason) = > console.log("reject", reason) // reject error
);

/ / output
// resolve success
// reject error
Copy the code

10. Catch method implementation

The promise.prototype.catch () method is an alias for. Then (null, Rejection) or. Then (undefined, Rejection) that specifies the callback when an error occurs.

Code implementation:

/ / catch
catch(onRejected) {
  this.then(undefined, onRejected);
}
Copy the code

Test it out:

const promise = new MyPromise((resolve, reject) = > {
  reject("error");
});

const p1 = promise
  .then((value) = > console.log("resolve", value))
  .catch((err) = > {
    console.log("catch", err);
  });

// Print catch error
Copy the code

11 finally code implementation

The finally() method is used to specify actions that will be performed regardless of the final state of the Promise object.

Code implementation:

// finally executes regardless of success or failure
finally(fn) {
  return this.then(
    // Successful state execution
    (value) = > {
      return MyPromise.resolve(fn()).then(() = > {
        return value;
      });
    },
    // Failed to execute
    (error) = > {
      return MyPromise.resolve(fn()).then(() = > {
        throwerror; }); }); }Copy the code

12. All method implementation

The All method returns a promise object that asks all the parameter states in the passed array to be fulfilled. The fulfilled state is fulfilled. If there is a Rejected state, the Fulfilled state is returned.

Analysis is as follows:

  1. Return a Promise: all = (promiseList) => new MyPromise(//…) ;
  2. All the states in the passed array will become the fulfilled state, then the fulfilled state will be returned, and the array passed in needs to be traversed. Mypromise.resolve (promiseList[I]).then((value) => {}, (reason) => {})
  3. The result is returned when the number of successes of promise.resolve () is equal to the length of the array passed in.

The code implementation is as follows:

static all(promiseList) {
  return new MyPromise((resolve, reject) = > {
    // Get the length of the array passed in
    const len = promiseList.length;
    // Initialize the result array
    const result = [];
    // Initialize the counter
    let count = 0;

    // Iterate over the passed method
    promiseList.forEach((promise) = > {
      MyPromise.resolve(promise).then(
        (value) = > {
          count++;
          result[index] = value;

          if (count === len) {
            returnresolve(result); }},(reason) = > reject(reason)
      );
    });
  });
}
Copy the code

13. Race method implementation

The RACE method, which returns a Promise object requiring that the first state to change for the passed parameter, whether it succeeds or fails, is the returned state.

Analysis:

  1. Return a promise, race = (promiseList) => new MyPromise(() => {});
  2. To iterate over the incoming promiseelist and get each of the incoming promiseItems, use the for loop because any return breaks the loop.
  3. Success or failure, if the first state changes, return, MyPromise.resolve(promiseList[i]).then((value) => { return reslove(value) }, (reason) => { return reject(reason); })

Code implementation:

// race static method that returns whoever completes first
static race(promiseList) {
  return new MyPromise((resolve, reject) = > {
    // Get the length of the array passed in
    const len = promiseList.length;

    if (len === 0) {
      resolve();
    } else {
      for (let i = 0; i < len; i++) {
        MyPromise.resolve(promiseList[i]).then(
          (value) = > {
            return resolve(value);
          },
          (reason) = > {
            returnreject(reason); }); }}}); }Copy the code

14. AllSettled method implementation

The 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.

Once each promise in the set of promises specified has been completed, unpromised promises will be completed asynchronously, whether successfully made or rejected. At that point, the returned promise processor will pass in as input an array of promises containing the results of each promise in the original Promises set.

For each result object, there is a status string. If its value is fulfilled, there is a value on the result object. If the value is rejected, there is a reason. Value (or Reason) reflects the value of each promise resolution (or rejection).

Analysis:

  1. Return a promise object, allSettled = (promiseList) => new MyPromise(() => {});
  2. Iterate over the promiseList and record the sum of the number of failures or successes.
  3. Returns when the sum of all numbers equals the length of the array.

Code implementation:

// allSettled static implementation
static allSettled(promiseList) {
  return new MyPromise((reslove, reject) = > {
    // Cache array length
    const len = promiseList.length;
    // Initialize the result array
    const result = [];
    // Initialize the counter
    let count = 0;

    // If an empty array is passed in, an empty array is returned
    if (len === 0) {
      resolve(result);
    } else {
      // iterate over the incoming array
      for (let i = 0; i < len; i++) {
        MyPromise.resolve(promiseList[i]).then(
          (value) = > {
            // Success increments the counter by 1
            count++;
            // Save the successful state and set the state to depressing
            result[i] = {
              status: "fulfilled",
              value,
            };

            // If the length of the array passed is equal to that of the technician, resolve is returned
            if (len === count) {
              returnresolve(result); }},(reason) = > {
            // Fail, counter increment 1
            count++;
            // Store the result of the failed state and set the return status to Rejected
            result[i] = {
              status: "rejected",
              reason,
            };

            // If the length of the array passed is equal to that of the technician, resolve is returned
            if (len === count) {
              returnresolve(result); }}); }}}); }Copy the code

At this point, a custom version of the Promise is complete. Use Promises – aplus-Tests to install and test it

I Promise A+ test

Promises that are implemented by hand need to comply with the Promise A+ specification, and promises-aplus-tests are required to fully comply with the Promise A+ specification.

1. Install the promises – aplus – tests

Yarn add promises- aplus-tests-d or NPM install promises- aplus-tests-dCopy the code

2. Add the test code Deferred

MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
};
Copy the code

3. Configure the package.json file

{
  "name": "my-promise"."version": "1.0.0"."description": ""."main": "my-promise.js".// Import file
  "scripts": {
    "test": "promises-aplus-tests my-promise" // Configure commands
  },
  "author": ""."license": "ISC"."devDependencies": {
    "promises-aplus-tests": "^ 2.1.2"}}Copy the code

4. Start the test

yarn test & npm run test
Copy the code

Effect:

conclusion

From the basic use of Promise, Promise A+, handwritten implementation of Promise, handwritten code testing and other aspects of the analysis of the underlying implementation of Promise. Promise is a solution to the asynchronous scheme. However, if the link is long, the code will also be difficult to maintain. For this situation, es6 proposes an asynchronous solution of Generator function and Async await. For more information, please check out the following articles.

It is not easy to write a long article, and the best encouragement is to support it.

Handwritten Promise has been put into warehouse MyPromise.

reference

  1. Ruan Yifeng teacher ES6 Promise
  2. Promise A+
  3. Classic INTERVIEW Questions: The most detailed handwritten Promise tutorial ever