preface

Promises are a solution to asynchronous programming: syntactically, promises are an object from which to retrieve messages for asynchronous operations; In its original sense, it is a promise that will give you results over time. Promise has three states: pending, fulfiled, and Rejected. Once the state has changed, it will never change. Once a Promise instance is created, it executes immediately.

Write promise implementations that comply with the promiseA+ specification

Before implementing it, take A look at the Promise A Plus specification

1. Create the Promise constructor


Here, the most basic function of promise is realized first: immediately execute after promise is created; Execute the corresponding function when then; The catch error immediately becomes reject.

// Promise has only one argument, called executorfunction Promise(executor) {
    let self = this;
    self.status = 'pending'; // wait state self.value = undefined; // The default success value self.err = undefined; // Default failed valuefunction resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved'; self.value = value; }}function reject(err) {
        if (self.status === 'pending') {
            self.status = 'rejected'; self.err = err; }} reject {reject (resolve, reject) {reject (resolve, reject); } catch (error) {reject(error); }} // Define on prototypethenInstance method promise.prototype. then =function (onFulfilled, onRejected) {
    let self = this;
    if (self.status === 'resolved') {
        onFulfilled(self.value);
    }
    if (self.status === 'rejected') { onRejected(self.err); }}Copy the code

Let’s test our Promise here

This is where the basic functionality is implemented, as promised is a solution for asynchronous programming; Let’s add asynchronous logic to run it:

2. Promise is invoked asynchronously


We all know that asynchronous code doesn’t execute immediately. It’s not resolved, it’s not Rejected, it’s pending.

In the previous state determination, a pending state was lost.

When the status is pending, onFulfilled and onRejected will be stored in the array first. When the status changes, we will go through the number group again to make the functions in it execute successively and see the code.

Field () onFulfiled(), onRejected(

function Promise(resolver) {
    let self = this;
    self.status = 'pending'; // wait state self.value = undefined; // The default success value self.err = undefined; // Default failed value self.onresolvedCallbacks = []; / / storethenSuccessful callback self.onrejectedCallbacks = []; / / storethenFailed callbackfunction resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved'; self.value = value; Self. OnResolvedCallbacks. ForEach (fn = > {/ / call the resolve, executed in sequence in the array function fn (); }}})function reject(err) {
        if (self.status === 'pending') {
            self.status = 'rejected'; self.err = err; self.onRejectedCallbacks.forEach(fn=>{ fn(); Resolver (resolve, reject); } Catch (error) {reject(error)}}Copy the code

(2) Add pending judgment to then method

Promise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    if (self.status === 'resolved') {
        onFulfilled(self.value);
    }
    if (self.status === 'rejected') {
        onRejected(self.err);
    }
    if(self.status==='pending') {/ / not resolved at this time, also didn't rejectd self. OnResolvedCallbacks. Push (() = > {onFulfilled (self. Value); }); self.onRejectedCallbacks.push(()=>{ onRejected(self.err); }}})Copy the code

Now, asynchronous logic

After 1s, the execution is successful, isn’t it amazing? Look at the following:

3. Promise chain call


(1) The specification states that “THEN” can be called multiple times in the same promise.

(2) jquery can implement chained calls by returning this, but promise cannot return this. It returns a new promise instance (note, not the original promise instance).

Create a new promise2 in then with a Promise for each state pack

This returns a new promise2, which also calls resolve or reject; Resolve (x) : resolve(x) : resolve(x) : resolve(x) : resolve(x) : resolve(x

(1) x may be a promise;

(2) May be an object or method;

(3) It may also be an ordinary value.

You need a method to handle x

3.1 Process the return values of onFulfilled and onRejected


Introduce a resolvePromise(promise2, x, resolve, reject); The four parameters here are

  1. Promise2 is the new promise that we return
  2. X is the return value of the previous THEN
  3. Resolve is the successful method
  4. Reject is a method of failure

It’s important to note that some people may write promises that both succeed and fail, and if both are called the first one is ignored. Add a call to resolvePromise(promise2, x, resolve, reject) :

function resolvePromise(promise2, x, resolve, reject) {
    if(promise2 === x) {//promise2 and x cannot be the samereturn reject(new TypeError('Circular reference'))}letcalled; // Whether the call succeeded or failed // here the type of x is judgedif(x ! == null && (typeof x ==='object' || typeof x === 'function'// Determine if x is a promise if x is an object and x'sthenThe method is a function and we think of it as a promiselet then = x.then;
            if (typeof then= = ='function') {
                then.call(x, function (y) {
                    if (called) return
                    called = trueResolvePromise (promise2, y, resolve, reject)}function(err) {// Failedif (called) return
                    called = truereject(err); })}else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true; reject(e); }}else{// specify a common value 1 resolve(x); }}Copy the code

Make some changes to the previous code accordingly

4. Value penetration problem


If nothing is passed in then, the value will pass through to the last call;

This will need to write a default function for onFulfilled and onRejected in then

 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function(err) { throw err; // We need to throw an error herereturnErr, otherwise the next call to success state}Copy the code

5. Asynchronous implementation of THEN


According to the specification, all the onFulfilled and onRejected must be performed asynchronously

Use resolve as an example to write setTimeout():

6. Please defer to me


To use a promise, we all need to start with new promise (), for example:

function read() {
    let fs = require('fs');
    let promise = new Promise(function(resolve,reject){
        fs.readFile('./1.txt'.'utf8'.function(err,data){
            if(err) reject(err); resolve(data); })});return promise
}
Copy the code

In Promise, it provides us with a syntax-sugar promise.defer. With promise.defer, just write:

function read() {
    let defer = Promise.defer()
    require('fs').readFile('.//1.txt'.'utf8'.function (err, data) {
        if(err) defer.reject(err);
        defer.resolve(data);
    })
    return defer.promise;
}
Copy the code

To do this, add a defer method to our Promise:

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}
Copy the code

Here, we basically implemented a fairly complete promise; Of course, Promise also has a lot of static methods, as well as the asynchronous history of JS, which can be discussed next time. Complete code:

// Promise has only one argument, called executorfunction Promise(executor) {
    let self = this;
    self.status = 'pending'; // wait state self.value = undefined; // The default success value self.err = undefined; // Default failed value self.onresolvedCallbacks = []; / / storethenSuccessful callback self.onrejectedCallbacks = []; / / storethenFailed callbackfunction resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(function(fn) { fn(); }); }}function reject(err) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.err = err;
            self.onRejectedCallbacks.forEach(function(fn) { fn(); }); }} reject {reject (resolve, reject) {reject (resolve, reject); } catch (error) {reject(error); }}function resolvePromise(promise2, x, resolve, reject) {
    if(promise2 === x) {//promise2 and x cannot be the samereturn reject(new TypeError('Circular reference'))}letcalled; // Whether the call succeeded or failed // here the type of x is judgedif(x ! == null && (typeof x ==='object' || typeof x === 'function'// Determine if x is a promise if x is an object and x'sthenThe method is a function and we think of it as a promiselet then = x.then;
            if (typeof then= = ='function') {
                then.call(x, function (y) {
                    if (called) return
                    called = trueResolvePromise (promise2, y, resolve, reject)}function(err) {// Failedif (called) return
                    called = truereject(err); })}else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true; reject(e); }}else{// specify a common value 1 resolve(x); }} // defined on prototypethenInstance method promise.prototype. then =function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function(err) { throw err; // We need to throw an error herereturnErr, otherwise the next call to success state}let self = this;
    letpromise2; // Return promiseif (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    letx = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})})}if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    letx = onRejected(self.err); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})})} // When calledthenIt may not succeed or failif (self.status === 'pending') {
        promise2 = new Promise(function(resolve, reject) {/ / at this point no resolve no reject self. OnResolvedCallbacks. Push (function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        letx = onRejected(self.err); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})}); })}return promise2;
}
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}

module.exports = Promise;

Copy the code

7. Promise test


npm i -g promises-aplus-tests
promises-aplus-tests Promise.js
Copy the code