Promise is a solution to asynchronous programming that makes more sense and is more powerful than traditional solutions — callback functions and events

Promise is widely used in our development and is also an important indicator to measure the level of front-end development in the interview. This article will lead you to write A Promise that meets the Promise/A+ specification hand in hand.

Let’s go!

The basic framework for implementing promises

ES6 specifies that a Promise object is a constructor that generates a Promise instance.

  • The Promise constructor takes a function as an argument.
  • The two arguments to this function are resolve and reject. They are two functions that are provided by the JavaScript engine and do not need to be deployed themselves.
  • The incoming execution function executes immediately.
function MyPromise(executor) { function resolve(result) { console.log('resolved', result); } function reject(reason) { console.log('rejected', reason); } try { executor(resolve, reject); } catch(error) { reject(error.message); }}Copy the code

Adding a State machine

This is a big pity. There are three states of Promise: Pending, fulfilled, and rejected.

  • The state of a Promise is irreversible: the default state is pending, and the state can only be changed from Pending to fulfilled or pending to Rejected.
  • The resolve function: changes the state of the Promise object from pending to depressing and passes the result of the asynchronous operation as a parameter.
  • Reject: Changes the state of the Promise object from Pending to Rejected. It is called when the asynchronous operation fails and passes the error reported by the asynchronous operation as a parameter.
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { let self = this; // Add state to the Promise constructor. The default state is pending self.state = pending; Function resolve(result) {// The Promise state will change from pending to fulfilled if (self.state === pending) {self.state == pending FULFILLED; console.log('resolved', result); Reject (reason) {reject(reject) {reject(reject) {reject(reject) = reject(reject) {reject(reject) = reject(reject) REJECTED; console.log('rejected', reason); } } try { executor(resolve, reject); } catch(error) { reject(error.message); }}Copy the code

Implement then methods

Promise instances have THEN methods, that is, then methods defined on the prototype object Promise.Prototype. It adds a callback function to the Promise instance when the state changes. The first argument to the THEN method is the resolved state callback and the second argument is the Rejected state callback, both of which are optional.

  • The then method can take two callback functions as arguments.
  • The first callback is called when the state of the Promise object becomes a pity
  • The second callback is called when the state of the Promise object changes to Rejected.
  • Both of these functions are optional and do not have to be provided. They all accept the value passed out from the Promise object as an argument.
  • The callback function passed in by THEN must wait for the Promise state to become a pity or Rejected before it can be called.
MyPromise. Prototype. Then = function(ondepressing, onRejected) {// This is a pity. OnFulfilled = typeof onFulfilled === 'function'? onFulfilled : () => { return this.value }; onRejected = typeof onRejected === 'function' ? onRejected : () => { return this.reason }; This. State === PENDING) {this onFulfilled; this.rejectedCallback = onRejected; } if (this.state === FULFILLED) { onFulfilled(); } if (this.state === REJECTED) { onRejected(); }};Copy the code

Implement chain calls

Promise supports chained writing, where a then method is followed by another then method. Then the current Promise is returned to the next THEN after the completion of the execution. If the Promise is successful, the next THEN success callback will be performed, and if the Promise fails, the next THEN failure callback will be performed.

  • The then method returns a new Promise instance.
  • The latter callback waits for the state of the previous Promise object to change before it can be called.
  • The callback function returned from the then method cannot be itself, and if it does, then the function will wait for the promise result inside, creating a callback hell with layers of state waiting.
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { const self = this; // Add state to the Promise constructor. The default state is pending self.state = pending; self.value = undefined; self.reason = undefined; self.onResolvedCallbacks = []; self.onRejectedCallbacks = []; Function resolve(result) {// The Promise state will change from pending to fulfilled if (self.state === pending) {self.state == pending FULFILLED; self.value = result; // Accept the value passed from the Promise object as an argument. self.onResolvedCallbacks.forEach(callback => { callback(); }); Reject (reason) {reject(reject) {reject(reject) {reject(reject) = reject(reject) {reject(reject) = reject(reject) REJECTED; self.reason = reason; / / accept Promise object value as a parameter from the self. The onRejectedCallbacks. ForEach (callback = > {callback (); }); } } try { executor(resolve, reject); } catch(error) { reject(error.message); } } MyPromise.prototype.then = function(onFulfilled, onRejected) { let self = this; // Both functions are optional and do not have to be provided. OnFulfilled = typeof onFulfilled === 'function'? onFulfilled : () => { return this.value }; onRejected = typeof onRejected === 'function' ? onRejected : () => { return this.reason }; Return new MyPromise((resolve, resolve, Reject) => {/** * then The callback must wait for the promise state to become a pity or Rejected before calling * If the promise is an asynchronous function, The state of the Promise is still pending. * At this time, we don't know whether to implement the onFulfilled callback or onRejected callback. Therefore, we will save these two callback functions first and call them according to the state of the final Promise after the Promise state changes. */ if (self.state === PENDING) { self.onResolvedCallbacks.push(function() { let result = onFulfilled(self.value); resolve(result); }); self.onRejectedCallbacks.push(function() { let result = onRejected(self.value); reject(result); }); } if (self.state === FULFILLED) { const result = onFulfilled(self.value); resolve(result); } if (self.state === REJECTED) { const reason = onRejected(self.reason); reject(reason); }}); };Copy the code

Promise resolver, improve promise.then()

In the above example, we assume that the passed THEN callback ondepressing and onRejected return normal values. In fact, the return values of the two callback functions can be various. For example, onFulfilled returns a promise, or returns a Thenable object or method. So we need to improve our code to be compatible with various return values.

Create the resolvePromise method to handle the various return values of onFulfilled and onRejected

// onFulfilled and onRejected return values are passed in by the developer, which may have various problems. There are a lot of different ways to return a value,
// So we need a function to process the various return values of onFulfilled and onRejected
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('Cannot loop'));
    }

    // There are then methods for objects or methods, known as thenable objects
    if (isThenable(x)) {
        let called;

        try {
            // Then can be an error
            let then = x.then;

            if (typeof then === 'function') {
                then.call(x, result= > {
                    if (called) {
                        return;
                    }

                    called = true;
                    After retrieving the return value of x.chen (), we still need to determine the return value, so we need to call resolvePromise again.
                    resolvePromise(x, result, resolve, reject);
                }, error= > {
                    if (called) {
                        return;
                    }

                    called = true;
                    reject(error);
                });
            } else{ resolve(x); }}catch (e) {
            if (called) {
                return;
            }

            called = true;
            reject(e);
        };
    } else{ resolve(x); }};Copy the code

Create isThenable to check whether an object or function has then methods

Function isThenable(t) {// if (t! == null && typeof t === 'object' || typeof t === 'function' && 'then' in t) { if (t ! == null && (typeof t === 'object' || typeof t === 'function')) { return true; } return false; }Copy the code

Promise.all()

Key points:

  1. The promise.all () method is used to wrap multiple Promise instances into a new Promise instance.
  2. Add the all method directly to the constructor
  3. The promise.all () method takes an array as an argument (it may not be an array, but it must have an Iterator interface, and each member returned is a Promise instance).
  4. We need to determine if each parameter in the array is a Promise. If it is, we execute the Promise’s then method, and place the returned value in the array result. If it is a normal value, we place the value in the array result
  5. Wait until all promises in the parameter array are fulfilled before returning the result
MyPromise.all = function(arr) { return new MyPromise((resolve, reject) => { if (! Array.isarray (arr)) {throw new TypeError(' promise.all must be an Array '); } let result = []; let count = 0; addResult = (key, value) => { count++; result[key] = value; Resolve (v); if (count === arr.length) {resolve(v); } } for (let i = 0; i < arr.length; i++) { let item = arr[i]; Function if (isThenable(item)) {try {let then = item.then; if (typeof then === 'function') { then(v => { addResult(i, v); }, e => reject(e)); } else { addResult(i, item); } } catch (e) { reject(e); }} else {// add the value to result (I, item); }}}); }Copy the code

Promise.race()

Key points:

  1. The promise.race () method again wraps multiple Promise instances into a new Promise instance.
  2. As soon as one instance changes state first, the entire promise state changes. The return value of the first changed Promise instance is passed to the Promise callback function.
Return new promise ((resolve, reject) => {for (let I = 0; i < arr.length; i++){ let item = arr[i]; if (isThenable(item)){ try { let then = item.then; if (typeof then === 'function') { then(resolve, reject); } else { resolve(item); } } catch (e) { reject(e); } } else { resolve(item); }}}); }Copy the code

Mind mapping

promise.then

resolvePromise

test

How do we test Promises/A+? The open source community provided a package to test our code: Promises -aplus-tests

Aplus-tests: NPM install Promises aplus-tests

2 Add the following code:

Deferred = myPromise.defer = function() {let deferred = {}; deferred.promise = new MyPromise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; }); return deferred; } module.exports = MyPromise;Copy the code

3 Change the package.json file

// Promises -aplus-tests: {"test": "promises-aplus-tests -promise.js",},Copy the code

4 Running Commands

npm run test
Copy the code

5 Check the test results, if all green, your code complies with Promises/A+ (unfinished promise.all() and promise.rase() will not affect the test results of the code we already have) screen Shot 2021-04-27 08.54.12pm

Final code (including test code):

Github.com/Aaron-Gym/K…

I hope this article can help students better understand the principle of Promise. If you have any doubts or do not understand, please feel free to leave a comment and discuss. If this article is helpful to students, please leave a like.