This article realizes a simple class to understand the Promise principle, compares Promise with traditional callback, shows Promise’s advantages in asynchronous programming, and finally introduces Promise’s application in actual development.

A concept.

Promise decouples asynchronous operations from callbacks and associates them with execution state. Promise is notified of the status after the asynchronous operation, and the Promise is responsible for triggering the callback function.

Ii. The Promise Principle

1. Status changes

Input:

let p0 = new Promise((resolve, reject) = > {})
console.log('p0', p0)

let p1 = new Promise((resolve, reject) = > {
    resolve('success')})console.log('p1', p1)

let p2 = new Promise((resolve, reject) = > {
    reject('failure')})console.log('p2', p2)

let p3 = new Promise((resolve, reject) = > {
    throw('error')})console.log('p3', p3)
Copy the code

Output:

Conclusion:

  1. The newly created Promise object is in a pending state.
  2. After the asynchronous operation is complete, call resolve/ Reject to change the status to fulfilled/ Rejected.
  3. When an exception (synchronization exception) occurs during the execution of the function, change the status to Rejected.

Implementation:

class MyPromise {
    constructor(executor) {

        this.initValue();
        // Since resolve/reject is called by the external executor function, this must be hard-bound to the current MyPromise object
        this.initBind();
        
        try {
            // Execute the function passed in
            executor(this.resolve, this.reject);
        } catch (e) {
            Execute reject when an error is caught
            this.reject(e); }}initBind() {
        this.resolve = this.resolve.bind(this);
        this.reject = this.reject.bind(this);
    }

    initValue() {
        this.PromiseResult = null;
        this.PromiseState = 'pending';
    }

    resolve(value) {
        // The status can only be changed from pending to pity/Rejected
        if (this.PromiseState == 'pending') {this.PromiseState = 'fulfilled';
            this.PromiseResult = value; }}reject(reason) {
         // The status can only be changed from pending to pity/Rejected
        if (this.PromiseState ! = ='pending') {this.PromiseState = 'rejected';
            this.PromiseResult = reason; }}}Copy the code

2. Execute the callback

Input/output:

// Immediately print "resolve= success"
const p1 = new Promise((resolve, reject) = > {
    resolve('success');
}).then(res= > console.log('resolve=',res), err= > console.log('reject=',err))

Reject = reject after 1 second
const p2 = new Promise((resolve, reject) = > {
    setTimeout(() = > {
        reject('failure');
    }, 1000)
}).then(res= > console.log('resolve=',res), err= > console.log('reject=',err))
Copy the code

Conclusion:

  • Then takes two arguments:Success callbackandFailure callback
  • When the Promise state isfulfilledperformSuccess callbackforrejectedperformFailure callback
  • You can do this by calling then multiple timesRegister multiple callback functions, pay attention to the distinction between chain calls

Implementation:

When registering a callback with THEN, if the Promise state is fulfilled/ Rejected, the callback function will be executed.

If the Promise state is pending, the callback function is saved and executed after the asynchronous operation ends.

initValue(){...// Save the callback function.
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
}

resolve(value){...// The status changes to depressing, which executes the successful callback of saving
    while (this.onFulfilledCallbacks.length) {
        this.onFulfilledCallbacks.shift()(this.PromiseResult); }}reject(reason){...// The status changes to Rejected, which executes the saved failed callback
     while (this.onRejectedCallbacks.length) {
         this.onRejectedCallbacks.shift()(this.PromiseResult); }}then(onFulfilled, onRejected) {
    // Make sure it is a function
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val= > val;
    onRejected = typeof onRejected === 'function' ? onRejected : reason= > { throw reason };
    if (this.PromiseState === 'fulfilled') {
        // Perform a big callback
        onFulfilled(this.PromiseResult);
    } else if (this.PromiseState === 'rejected') {
        // Execute the Rejected callback
        onRejected(this.PromiseResult);
    }else if (this.PromiseState === 'pending') {
        // Promise is in a pending state and holds two callbacks temporarily
       this.onFulfilledCallbacks.push(onFulfilled);
       this.onRejectedCallbacks.push(onRejected); }}Copy the code

3. Chain call

Input/output

// the chain-call output is 200
const p3 = new Promise((resolve, reject) = > {
    resolve(100);
}).then(res= > 2 * res)
  .then(res= > console.log(res))

// the chain-call output is 300
const p4 = new Promise((resolve, reject) = > {
    resolve(100);
}).then(res= > new Promise((resolve, reject) = > resolve(3 * res)))
  .then(res= > console.log(res))
Copy the code

conclusion

  1. The then method itself returns a new Promise object.

  2. If the callback function returns a Promise object, the state of the new Promise object is determined by that Promise object.

  3. If the callback function returns a value other than a Promise object, the new Promise object is in a successful state.

implementation

 then(onFulfilled, onRejected){...var thenPromise = new MyPromise((resolve, reject) = > {
         const resolvePromise = cb= > {
             try {
                 const x = cb(this.PromiseResult)
                     if(x === thenPromise){
                         // wait for yourself to complete, loop waiting: then returns the value of then in the then callback.
                         reject(new TypeError('Chaining cycle detected for promise'));
                     }
                     if (x instanceof MyPromise) {
                         // If the return value is a Promise object, the new Promise state is determined by that Promise.
                         x.then(resolve, reject);
                    } else {
                        // Either Promise will succeedresolve(x); }}catch (err) {
                    // Handle an errorreject(err); }}if (this.PromiseState === 'fulfilled') {
                // If the current state is successful, execute the first callback
                resolvePromise(onFulfilled);
            } else if (this.PromiseState === 'rejected') {
                // If the current state is failed, perform the second callback
                resolvePromise(onRejected);
            } else if (this.PromiseState === 'pending') {
                // If the state is pending, save two callbacks for now
                this.onFulfilledCallback = resolvePromise(onFulfilled);
                this.onRejectedCallback = resolvePromise(onRejected); }})// Return the wrapped Promise
        return thenPromise;

    }
Copy the code

4. Call timing

Input and output

setTimeout(() = >{console.log(0)},0);
const p = new Promise((resolve, reject) = > {
    console.log(1);
    resolve()
}).then(() = > console.log(2))

console.log(3)

// The output order is 1, 3, 2, 0
Copy the code

conclusion

  1. Even if the state of the Promise is immediately updated as fulfilled, the callback function will not be executed immediately.
  2. The callback function is executed in the microtask of the event loop.
  3. Because callbacks are inherently asynchronous, placing them on microtasks allows later synchronized code to be executed as quickly as possible.

implementation

const resolvePromise = cb= > {
    setTimeout(() = > {
       // Execute callback...})}Copy the code

Promise vs. traditional callback

1. Callback hell

In a scenario where multiple asynchronous operations are performed, traditional callbacks need to pass in callback functions as arguments, causing the problem of “callback hell”. Promise decouples the asynchronous operation from the callback function, eliminating the need to pass in the callback function in the first place.

// Traditional callbacks implement multiple asynchronous operations
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);
// Promise implements multiple asynchronous operations
doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
Copy the code

2. Catch exceptions

Note: Exceptions here refer to callback function exceptions, not asynchronous operation exceptions.

// the following is a typical timeoutCallback. Because timeoutCallback is called in an asynchronous operation, try catch cannot catch the asynchronous exception.
try{
    setTimeout(() = >timeoutCallback("3 seconds passed"), 3000);
}catch(err){
    // The timeoutCallback exception cannot be caught.
    console.log(err);
}

// Promise is responsible for calling the callback. When the asynchronous operation ends and the Promise state changes, the Promise invokes the callback with a try catch.
const wait = ms= > new Promise(resolve= > setTimeout(resolve, ms));
wait(3000).then(() = > timeoutCallback("3 seconds passed")).catch(err= >{console.log(err)});
Copy the code

Iv. Promise application

1. Create completed promises

PromiseAnd the resolve ()Promise.reject() 
Copy the code

2. Encapsulate traditional callbacks

Since traditional callbacks are plagued by callback hell and lack of asynchronous exception catching, promises are used to encapsulate existing traditional callbacks.

The following is a typical timeoutCallback. Because the timeoutCallback is executed asynchronously, the exception in the asynchronous callback cannot be caught.
try{
    setTimeout(() = >timeoutCallback("3 seconds passed"), 3000);
}catch(err){
    // The timeoutCallback exception cannot be caught.
    console.log(err);
}

// Promise can separate the asynchronous operation from the callback and automatically add a try catch to the callback function when the callback is called after the asynchronous operation completes.
const wait = ms= > new Promise(resolve= > setTimeout(resolve, ms));
wait(3000).then(() = > timeoutCallback("3 seconds passed")).catch(err= >{console.log(err)});
Copy the code

3. Perform multiple asynchronous operations

// Parallel multiple asynchronous operations and wait until all operations are complete to proceed to the next operation
Promise.all([promise1, promise2, promise3])
.then(([result1, result2, result3]) = > { 
    // next step 
});
.catch(err){
    Reject (reject); reject (reject); reject (reject);
}

// Perform multiple asynchronous operations in sequence (chain calls)
[promise1, promise2, promise3].reduce((pre, cur) = > pre.then(() = >{return cur}), Promise.resolve())
.then(result3= > {
    /* use result3 */ 
}).catch(err= >{
    Reject (reject); reject (reject); reject (reject);
});
// In ES2017, the above code can also be optimized with async/await
let result;
try{
    for (const pro of [promise1, promise1, promise1]) {
        result = awaitpro(result); }}catch(err){
    result = err;
}
Copy the code

reference

Read it, write it by hand, the most accessible version of the Promise principle

Use promises to encapsulate the worker