Promise is a very common question in front-end interviews. When I was an interviewer, I asked Promise more than 90 percent of the time, and most companies, as far as I know, asked some kind of Promise question. If you can write code that complies with the PromiseA+ specification, I think you’ll be able to answer all the Promise questions in the interview.

My suggestion is that the specification to write more than a few implementation, perhaps the first time, is changed many times, to pass the test, then need to repeatedly write, I have Promise source code implementation wrote more than seven times.

More articles can be read: github.com/YvetteLau/B…

Promise source code implementation

/** * 1. When a new Promise is passed, an executor executor is passed and the executor executes immediately. 3. Promise can only be changed from pending to rejected, or from pending to depressing * 4. Once the state of promise is confirmed, OnFulfilled, * and onRejected * 6. This is a big promise, which is a pity. This is a big promise, which promises to fail onFulfilled * 6. If the promise is successful when calling THEN, ondepressing will be performed and the promise value will be passed in as a parameter. * If the promise has failed, then execute onRejected and pass in the reason why the promise failed. * If the promise state is pending, the ondepressing and onRejected functions need to be stored. After the waiting state is determined, Ondepressing and onRejected can default * 8. Promise can be fulfilled several times, and the promise's then method returns a promise * 9. This is a big pity. If then returns a result, this result will be passed as a parameter to the successful callback of the next THEN. If an exception is thrown in THEN, it is passed as an argument to the onRejected * 11 callback of the next THEN. If then returns a promise, then the promise is executed. If the promise succeeds, then the next THEN succeeds. If the promise fails, then the next THEN fails

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(executor) {
    let self = this;
    self.status = PENDING;
    self.onFulfilled = [];// Successful callback
    self.onRejected = []; // Failed callback
    / / PromiseA + 2.1
    function resolve(value) {
        if (self.status === PENDING) {
            self.status = FULFILLED;
            self.value = value;
            self.onFulfilled.forEach(fn= > fn());/ / PromiseA + 2.2.6.1}}function reject(reason) {
        if (self.status === PENDING) {
            self.status = REJECTED;
            self.reason = reason;
            self.onRejected.forEach(fn= > fn());/ / PromiseA + 2.2.6.2}}try {
        executor(resolve, reject);
    } catch(e) { reject(e); }}Promise.prototype.then = function (onFulfilled, onRejected) {
    //PromiseA+ 2.2.1 /PromiseA+ 2.2.5/2.2.7.3/2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason= > { throw reason };
    let self = this;
    / / PromiseA + 2.2.7
    let promise2 = new Promise((resolve, reject) = > {
        if (self.status === FULFILLED) {
            / / PromiseA + 2.2.2
            / / PromiseA + 2.2.4 - setTimeout
            setTimeout((a)= > {
                try {
                    / / PromiseA + 2.2.7.1
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    / / PromiseA + 2.2.7.2reject(e); }}); }else if (self.status === REJECTED) {
            / / PromiseA + 2.2.3
            setTimeout((a)= > {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) { reject(e); }}); }else if (self.status === PENDING) {
            self.onFulfilled.push((a)= > {
                setTimeout((a)= > {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) { reject(e); }}); }); self.onRejected.push((a)= > {
                setTimeout((a)= > {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) { reject(e); }}); }); }});return promise2;
}

function resolvePromise(promise2, x, resolve, reject) {
    let self = this;
    / / PromiseA + 2.3.1
    if (promise2 === x) {
        reject(new TypeError('Chaining cycle'));
    }
    if (x && typeof x === 'object' || typeof x === 'function') {
        let used; //PromiseA+2.3.3.3.3 can be invoked only once
        try {
            let then = x.then;
            if (typeof then === 'function') {
                / / PromiseA + 2.3.3
                then.call(x, (y) => {
                    / / PromiseA + 2.3.3.1
                    if (used) return;
                    used = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, (r) => {
                    / / PromiseA + 2.3.3.2
                    if (used) return;
                    used = true;
                    reject(r);
                });

            }else{
                / / PromiseA + 2.3.3.4
                if (used) return;
                used = true; resolve(x); }}catch (e) {
            / / PromiseA + 2.3.3.2
            if (used) return;
            used = true; reject(e); }}else {
        / / PromiseA + 2.3.3.4resolve(x); }}module.exports = Promise;

Copy the code

There are test scripts that test whether code is written in compliance with the PromiseA+ specification.

First, add the following code to the promise implementation code:


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

Install the test script:

npm install -g promises-aplus-tests
Copy the code

If the current promise source file is promise.js

Execute the following command in the corresponding directory:

promises-aplus-tests promise.js
Copy the code

Promises – Aplus-Tests contains 872 test cases. The above code passes all use cases perfectly.

A quick note on the code implementation above (some of the other things are made clear in the comments):

  1. Ondepressing and ondepressing calls need to be placed in setTimeout, because it is stated in the specification: OnFulfilled or onRejected must not be called until the execution context stack contains only platform code. Using setTimeout only simulates asynchrony; native promises are not implemented this way.

  2. In the function of resolvePromise, the usEDD flag is needed because it is clearly stated in the specification: If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. Therefore, we need such a flag to ensure that it is executed only once.

  3. The successful callback and the failed callback are stored in self_onfulfilled and self_onRejected. According to specification 2.6, when the promise changes from the pending state, the callback corresponding to the THEN needs to be specified in order.

PromiseA+ Specification (Translated version)

PS: The following is my translation specification for your reference

The term

  1. A promise is an object or function that has then methods and behaves according to this specification
  2. Thenable is an object or function that has then methods
  3. Value is the successful value of the PROMISE state, including undefined/thenable or PROMISE
  4. Exception is an exception thrown with a throw
  5. Reason is the value when the promise state fails

requirements

2.1 Promise States

A Promise must be in one of three states: Pending, depressing, or Rejected

2.1.1 If the Promise is in the Pending state
This can become a pity or rejectedCopy the code
2.1.2 If the promise is fulfilled
2.1.2.1 Does not Change to other States 2.1.2.2 There must be a valueCopy the code
2.1.3 If the Promise is in the Rejected State
2.1.3.1 Does not change into other states 2.1.3.2 There must be a reason that promises are rejectedCopy the code

The state of the promise can only change from pending to depressing or from pending to rejected. Promise succeeds and has a successful value. If the promise fails, there is a reason for failure

2.2 then method

A promise must provide a THEN method to access the final result

Promise’s then method takes two arguments

promise.then(onFulfilled, onRejected)
Copy the code
2.2.1 onFulfilled and onRejected are both optional parameters
2.2.1.1 onFulfilled must be a function type 2.2.1.2 onFulfilled must be a function typeCopy the code
2.2.2 If ondepressing is a function:
2.2.2.1 Ondepressing must be called when the promise becomes a big pity, and the parameter is the value of the promise 2.2.2.2 Before the promise state is not a big pity, 2.2.2.3 Ondepressing can only be called onceCopy the code
2.2.3 If onRejected is a function:
2.2.3.1 onRejected must be called when the Promise becomes Rejected. The parameter is the promise's reason 2.2.3.2 Before the state of the Promise is not Rejected, 2.2.3.3 onRejected cannot be called only onceCopy the code
OnFulfilled and onRejected should be a micro-task
2.2.5 onFulfilled and onRejected must be called as functions
The 2.2.6 then method may be called multiple times
2.2.6.1 If the promise becomes a big pity, all the onFulfilled callback needs to be implemented according to the order of THEN 2.2.6.2 If the Promise becomes the Rejected state, All onRejected callbacks need to be executed in the order of THENCopy the code
2.2.7 THEN must return a Promise
promise2 = promise1.then(onFulfilled, onRejected);
Copy the code
2.2.7.1 onFulfilled or onRejected The result is X, and the resolvePromise is called 2.2.7.2 If onFulfilled or onRejected 2.2.7.3 This is a big pity if onFulfilled is not a function, and the promise2 will be fulfilled with the value of promise1. 2.2.7.4 This is a big pity if onFulfilled is not a function. Promise2 indicates the reason rejected of promise1Copy the code

2.3 resolvePromise

resolvePromise(promise2, x, resolve, reject)

Reject promise with a TypeError if promise2 and X are equal
2.3.2 If X is a promsie
2.3.2.1 If X is pending, Then the promise must be pending until X becomes a pity or rejected. Fulfill promise with the same value. 2.3.2.3 If X is rejected, reject promise with the same reason.Copy the code
2.3.3 如果 x 是一个 object 或者 是一个 function
2.3.3.1 let then = x. teng. 2.3.3.2 Reject promise with eas the reason.. 2.3.3.3 If THEN is a function, Call (x, resolvePromiseFn, rejectPromise) 2.3.3.3.1 Execute resolvePromiseFn (promise2, y, resolve, reject); 2.3.3.3.3 If both resolvePromise and rejectPromise are invoked, the first call takes precedence. Subsequent calls are ignored. 2.3.3.3.4 If calling THEN throws an exception e 2.3.3.3.4.1 If resolvePromise or rejectPromise has been called, ignore 2.3.3.3.4.3 Otherwise, Reject promise with eas the reason 2.3.3.4 If then is not a function. Fulfill promise with X.Copy the code
2.3.4 If X is not an object or function fulfill promise with X.

Other methods of Promise

While the promise source code already complies with the PromiseA+ specification, the native promise provides other methods, such as:

  1. Promise.resolve()
  2. Promise.reject()
  3. Promise.prototype.catch()
  4. Promise.prototype.finally()
  5. Promise.all()
  6. Promise.race()

The following details the implementation of each method:

Promise.resolve

Promise.resolve(value) returns a Promise object resolved with the given value.

  1. If value is a Thenable object, the returned promise will “follow” the Thenable object and adopt its final state
  2. If the value passed in is itself a Promise object, promise.resolve will return the promise object unchanged.
  3. Otherwise, a Promise object with that value as a success status is returned directly.
Promise.resolve = function (param) {
        if (param instanceof Promise) {
        return param;
    }
    return new Promise((resolve, reject) = > {
        if (param && typeof param === 'object' && typeof param.then === 'function') {
            setTimeout((a)= > {
                param.then(resolve, reject);
            });
        } else{ resolve(param); }}); }Copy the code

The reason for adding setTimeout to the thenable object is inferred from the results of the native Promise object execution, as shown in the following test code: 20, 400, 30; For the same order of execution, a setTimeout delay was added.

Test code:

let p = Promise.resolve(20);
p.then((data) = > {
    console.log(data);
});


let p2 = Promise.resolve({
    then: function(resolve, reject) {
        resolve(30); }}); p2.then((data) = > {
    console.log(data)
});

let p3 = Promise.resolve(new Promise((resolve, reject) = > {
    resolve(400)})); p3.then((data) = > {
    console.log(data)
});
Copy the code

Promise.reject

Reject () differs from promise.resolve in that the arguments to the promise.reject () method are left as reject arguments to subsequent methods.

Promise.reject = function (reason) {
    return new Promise((resolve, reject) = > {
        reject(reason);
    });
}
Copy the code

Promise.prototype.catch

Promise.prototype.catch is a special then method used to specify an error callback

Promise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected);
}
Copy the code

Promise.prototype.finally

Success or failure will always go to finally, and after finally, can continue then. And passes the value exactly as it should to the subsequent then.

Promise.prototype.finally = function (callback) {
    return this.then((value) = > {
        return Promise.resolve(callback()).then((a)= > {
            return value;
        });
    }, (err) => {
        return Promise.resolve(callback()).then((a)= > {
            throw err;
        });
    });
}
Copy the code

Promise.all

Promise. All (Promises) return a Promise object

  1. If the argument passed in is an empty iterable, then the promise callback completes (resolve), and only then, is executed synchronously; all else is returned asynchronously.
  2. If the parameters passed in do not contain any promises, an asynchronous completion is returned.
  3. Promises all promises in Promises are made when promises are “made” or when arguments do not contain promises are called back.
  4. If one of the arguments fails, the promise object returned by promise.all fails
  5. In any case, promise.all returns an array as the result of the completion state of the Promise
Promise.all = function (promises) { promises = Array.from(promises); Return new Promise((resolve, reject) => {let index = 0; let result = []; if (promises.length === 0) { resolve(result); } else { function processValue(i, data) { result[i] = data; if (++index === promises.length) { resolve(result); } } for (let i = 0; i < promises.length; Resolve (Promise [I]). Then ((data) => {processValue(I, data); }, (err) => { reject(err); return; }); }}}); }Copy the code

Test code:

var promise1 = new Promise((resolve, reject) = > {
    resolve(3);
})
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100.'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values); //[3, 42, 'foo']
},(err)=>{
    console.log(err)
});

var p = Promise.all([]); // will be immediately resolved
var p2 = Promise.all([1337."hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
console.log(p);
console.log(p2)
setTimeout(function(){
    console.log('the stack is now empty');
    console.log(p2);
});
Copy the code

Promise.race

The promise. race function returns a Promise that will be completed in the same way as the first Promise passed. It may be a high degree of completion or rejection, depending on which of the two is the first.

If the passed array of parameters is empty, the returned promise will wait forever.

If the iteration contains one or more non-promise values and/or resolved/rejected promises, promise.race resolves to the first value found in the iteration.

Promise.race = function (promises) {
    promises = Array.from(promises);// Convert an iterable to an array
    return new Promise((resolve, reject) = > {
        if (promises.length === 0) {
            return;
        } else {
            for (let i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i]).then((data) = > {
                    resolve(data);
                    return;
                }, (err) => {
                    reject(err);
                    return; }); }}}); }Copy the code

Test code:

Promise.race([
    new Promise((resolve, reject) = > { setTimeout((a)= > { resolve(100)},1000)}),undefined.new Promise((resolve, reject) = > { setTimeout((a)= > { reject(100)},100) })
]).then((data) = > {
    console.log('success ', data);
}, (err) => {
    console.log('err ',err);
});

Promise.race([
    new Promise((resolve, reject) = > { setTimeout((a)= > { resolve(100)},1000)}),new Promise((resolve, reject) = > { setTimeout((a)= > { resolve(200)},200)}),new Promise((resolve, reject) = > { setTimeout((a)= > { reject(100)},100) })
]).then((data) = > {
    console.log(data);
}, (err) => {
    console.log(err);
});
Copy the code

Thanks for pointing out, add reference link

  • Promise A + specification
  • Documentation for ES6 Promise (es6.ruanyifeng.com/#docs/promi…)
  • Promise MDN documentation (developer.mozilla.org/zh-CN/docs/…)

Pay attention to the public number, join the technical exchange group