Promises are a solution to asynchronous programming. Promises are simply a container that holds the result of some event (usually an asynchronous operation) that will end in the future.

Promises makes this abstract definition of a single asynchronous operation, as shown below.

  • The Promise operation will only be in one of three states: incomplete, complete, or failed.
  • The Promise state can only be changed from an incomplete state to a completed state or a failed state, and cannot be reversed. The completed and failed states cannot convert to each other.
  • Once the state of a Promise is changed, it cannot be changed.

Promise constructor

Promise() uses a simple constructor interface to make it easy for users to create Promise objects:

/* The executor function to be declared by the user */
executor = function(resolve, reject) {
    if(/* Asynchronous operation succeeded */){
        resolve(data);
    }else {
        reject(err)
    }
}
/* Creates the constructor for the Promise object */
var promise = new Promise(executor)Copy the code

The Promise constructor takes a function as an argument, resolve and reject. They are two functions that are provided by the JavaScript engine and do not need to be deployed themselves.

  • Resolve (‘ Resolved ‘, ‘Resolved’, ‘Resolved’); resolve (‘ Resolved ‘, ‘Resolved’, ‘Resolved’);
  • Reject (reject) is called Pending–>Rejected and passes the error from the asynchronous operation as a parameter.

Executor () is a user-defined executor function. When the JavaScript engine creates a promise object with a new operation, it actually creates a new instance of the promise object before calling executor(), and gets the resolve() and reject() functions associated with that instance. Next, it calls executor(), passing resolve() and reject() as entry arguments, and the executor() function is executed until exit.

The user code in executor() can use the above two valuers to set the “data to be proffered” from the Promise object. That is, the process of binding the value for the Promise object is triggered by user code. This process looks like “getting user code to call back to the JavaScript engine.” Such as:

/* The user calls the engine back and forth with code (resolve or reject) to set the value */
new Promise(function (resolve, reject){
    resolve(100)})Copy the code

Finally, the resolve() valuer in the executor() function can accept any value — except the current promise itself. JavaScript throws an exception when it tries to set a value with itself.

Then method

After the Promise instance is generated, you can use the THEN method to specify the Resolved and Rejected state callback functions, respectively. It adds user code to the Promise instance that needs to be executed after a state change.

promise.then(function(res)  {
    console.log(res)
}, function(err){
    console.log(err)
})
Copy the code

Promises address the heart of asynchrony

It is also true that the user executor function passed to promise calls resolve(100) directly, without any asynchronous logic, so the principle by which promises handle asynchronous functions is not built into the Promise mechanism.

/* The user calls the engine back and forth with code (resolve or reject) to set the value */
new Promise(function (resolve, reject){
    resolve(100)})Copy the code

In other words, there is no delay, no delayed behavior, and no control over the dimension of “time” in the Promise mechanism. So when you create a promise in JavaScript, the creation process is immediate; Using the prototype promise.xxx method to get a new promise (promise2) is also done immediately.

Similarly, all promise objects are generated as soon as you need them, and importantly, the value/data these promises represent is not yet “Ready.” This readiness process is deferred to the “unknown future”. Once the data is ready, foo in promise.then (foo) is fired.

In other words, the heart of Promise’s handling of asynchronous logic is that you can pass an asynchronous executor function to the Promise constructor, call resolve on success, pass the response to the constructor and store it inside resolve, and call reject on failure. Combined with the then method, once the asynchronous function passed in the Promise returns successfully, the Resolved state callback passed by the user will be triggered **. **

In other words, the user doesn’t care when the result will return. They just want to call the Resolved callback function when it has something to return.

Promise is a model for parallel execution at the language level. It encapsulates a “time-stripped data” and proxies all behavior on that data. Because this data is time-stripped, the behavior applied to it is also non-sequential and parallel.

Promises themselves do not have a “parallel execution” feature. Their Promise instances encapsulate data triggers: when data is Ready, Actions are triggered. The latter (action or reaction) is the true logic of execution.

So the correct way to write the Hello World program in the Promise context is:

Promise.resolve('hello world') // data Ready ?
  .then(res= > console.log(res)) // call Action?
Copy the code

A Promise serves as a constructor, which is intended to add (resolving or binding) a futurel function to a concrete Promise instance.

A simplified Promsie implementation

That’s pretty much all we have to say about the core mechanics of promises, which internally hold a state container that holds the result of some event (usually an asynchronous operation) that will end in the future.

A Promise usually consists of two parts, the Promise constructor and then methods based on the prototype chain implementation.

  • The Promise constructor accepts an executor function that the user calls back and forth to the engine through code (resolve or Reject) to set the value.
  • The then method adds the user code ResolveCallback and RejectCallback to execute after the state change to the Promise instance.

Based on the above discussion, we can simulate the basic implementation:

Promsie constructor
  1. The Promise constructor accepts an executor as an executor function, usually wrapped around an asynchronous operation.
  2. The Promise constructor internally holds the state and data asynchronous function results, and defines the resolve and reject functions.
  3. Call executor and pass the resolve and reject methods inside the constructor as the actual arguments to the function. Start the set operation

Then method
  1. The THEN method is called when the state of a Promise changes, whether it succeeds or fails. Its job is to add a state change callback to the Promise instance and execute it when the asynchrony completes.
  2. When the then method is called, it is not certain which state the current promise is in, because the three states are handled separately
    • If the state has become the fulfilled state, run the ResolveCallback passed by the user
    • If state has changed to Failed, run the RejectCallback passed in by the user
    • When executing then, the asynchronous function may not have finished executing yet, and the state is still pending. In this case, the callback function is saved for execution when the state changes. The state change depends on the user passing in the executor function, so back inside the constructor you need to add the ResolvedCallbacks queue and RejectedCallbacks queue to hold the callback function, and when the state changes you need to manually invoke the corresponding callback function

After analysis, the corresponding code is as follows:

// myPromise.js
function Promise (executor) {
    this.state = 'pending';
    this.data = undefined;
    this.reason= undefined;
    this.resolvedCallbacks=[];
    this.rejectedCallbacks=[];
    let self = this
    // Save the internal state and response data, and call the successful callback function in turn
    let resolve = value= > {
        if(this.state === 'pending') {this.state = 'fulfilled';
            this.data = value;
            for(let i = 0; i< self.resolvedCallbacks.length; i++){ self.resolvedCallbacks[i](value); }}};// Save the internal state and response data, and call the failed callback in turn
    let reject = reason= > {
        if(this.state === 'pending') {this.state = 'failed';
            this.reason = reason;
            for (let i = 0; i < self.rejectedCallbacks.length; i++) { self.rejectedCallbacks[i](reason); }}};try {
        // Set the value to the promise object "that data it projects"
        // The callback function passed in by the user (accept resolve, reject)
        executor(resolve, reject);
    } catch(err) { reject(err); }}Promise.prototype.then = function(fn1, fn2) {
    var self = this
    var promise2
  
    // Check fn1 and fn2
    fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
    fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
  
    if (self.state === 'fulfilled') {
          // Put fn1 and fn2 in a try catch. After all, fn1 and fn2 are passed by the user
          try {
              var x = fn1(self.data)
          } catch (e) {       
          }
    }
  
    if (self.state === 'rejected') {
          try {
              var x = fn2(self.data)
          } catch (e) {
          }
    }
  
    if (self.state === 'pending') {
        // When executing then, the asynchronous function may not have completed execution, and the state is still pending
      	// Save the Resolved callback and the Rejected callback
      	self.resolvedCallbacks.push(function(value){
            try {
                var x = fn1(self.data);
            } catch (e) {
            }
        })
        self.rejectedCallbacks.push(function(value) {
            try {
                var x = fn2(self.data);
            } catch (e) {
            }
        })
    }
  }
  
  module.exports = Promise
Copy the code

At this point, a simplified version of the promise has been implemented.

Verify our handwritten Promsie

// mydemo.js
const Promise = require('./myPromise.js')

// The Promsie constructor turns into a callback that is a data valuer
// Accept resolve and reject on success and reject on failure
// The user calls the engine back and forth with code (resolve or reject) to set the value
const p = new Promise(function (resolve, reject){
    // A timer is set, and the corresponding status will be known after 1s
    try{
        setTimeout(() = > {
            resolve('I'm executed in 1s.')},1000);
    } catch (e) {
        reject(e)
    }
})

// User code that needs to be executed when the state of the Promise changes
// then is executed when the state is not determined
p.then((res) = > {
    console.log(res)
}, (e) = > {
    console.log(e)
})
Copy the code

The results are as expected

Promsie implementation that contains the THEN chain

Let’s say we have a scenario with multiple asynchronous interdependencies, where the latter asynchronous process depends on the former, for which promise provides a chained invocation. The chain call supports the promise to return a new promise each time, and the result of the response is passed to the new promsie as a callback function.

The sequentially executed relationship between two Promise objects is known in JavaScript as a “Then chain.” The “chain” relationship between the current promise object and the next promise2 by calling P.teng (), which in fact represents the sequential execution relationship between them, is the basic usage and key mechanism of the promise mechanism.

P.teng () represents the understanding of sequential logic, and it implies that the data represented by promise2 and promise are related.

Thus, from a macro perspective, it is the result Ready action that triggers thenable. The so-called “Thenable behavior” is to invoke the subsequent promise setters of “Then chain” and trigger the chain “Thenable behavior”.

Finally, in p.teng (), it does three main things:

  • Create a new promise2 object And,
  • Register the relationship between P and promise2; And then,
  • Associate ResolvedCallbacks and RejectedCallbacks to the resolve valuer of promise2.

Therefore, the transformation of the promise implemented above is mainly to return a new Promise function for the processing in THEN, and the callback function is successfully called and returned for the value processing. Then chain execution flow becomes as follows:

The complete code is as follows

// myPromise.js
function Promise (executor) {
    this.state = 'pending';
    this.data = undefined;
    this.reason= undefined;
    this.resolvedCallbacks=[];
    this.rejectedCallbacks=[];
    let self = this
    // Save the internal state and response data, and call the successful callback function in turn
    let resolve = value= > {
        if(this.state === 'pending') {this.state = 'fulfilled';
            this.data = value;
            for(let i = 0; i< self.resolvedCallbacks.length; i++){ self.resolvedCallbacks[i](value); }}};// Save the internal state and response data, and call the failed callback in turn
    let reject = reason= > {
        if(this.state === 'pending') {this.state = 'failed';
            this.reason = reason;
            for (let i = 0; i < self.rejectedCallbacks.length; i++) { self.rejectedCallbacks[i](reason); }}};try {
        // Set the value to the promise object "that data it projects"
        // The callback function passed in by the user (accept resolve, reject)
        executor(resolve, reject);
    } catch(err) { reject(err); }}Promise.prototype.then = function(fn1, fn2) {
    var self = this
    var promise2
  
    // Check fn1 and fn2
    fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
    fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
  
    if (self.state === 'fulfilled') {
        return promise2 = new Promise(function(resolve, reject) {
            // Put fn1 and fn2 in a try catch. After all, fn1 and fn2 are passed by the user
            try {
                var x = fn1(self.data)
                // Fn1 returns a value that is injected into the promise returned by THEN via resolve
                resolve(x)
            } catch (e) {
                reject(e)                
            }
        })
    }
  
    if (self.state === 'failed') {
        return promise2 = new Promise(function(resolve, reject) {
            try {
                var x = fn2(self.data)
                resolve(x)
            } catch (e) {
                reject(e)
            }
        })
    }
  
    if (self.state === 'pending') {
        return promise2 = new Promise(function(resolve, reject) {
            self.resolvedCallbacks.push(function(value){
                try {
                    var x = fn1(self.data);
                    resolve(x)
                } catch (e) {
                    reject(e)
                }
            })
            self.rejectCallbacks.push(function(value) {
                try {
                    var x = fn2(self.data);
                    resolve(x)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
  }
Copy the code

Verify that PROMsie supports the chained invocation feature.

const Promise = require('./myPromise.js')

const p = new Promise(function (resolve, reject){
    try{
        setTimeout(() = > {
            resolve('I'm executed in 1s.')},1000);
    } catch (e) {
        reject(e)
    }
})

p.then((res) = > {
    console.log('p1', res)
    return 'Callback result successful'
}).then((res) = > {
    console.log('p2', res)
})
Copy the code

Note that the next then chain returns the result of the previous then chain callback. If the previous THEN chain callback did not return a value, undefined will be returned

When the then function returns a promise, the code above returns the promise object,

For example, modify mydemo.js:

Const Promise = require('./ mypromise.js ') const p = new Promise(function (resolve, reject){ Try {setTimeout(() => {resolve(' I will be executed after 1s ')}, 1000); } catch (e) { reject(e) } }) p.then((res) => { console.log('p1', Return new Promise(function (resolve, reject){try{setTimeout(() => {resolve(' I will be done after 2s ')}, 1000); } catch (e) { reject(e) } }) }).then((res) => { console.log('p2', res) })Copy the code

In order to get the desired effect, it is necessary to judge separately:

// myPromise.js ... Promise.prototype. Then = function(fn1, fn2) {var self = this var self Fn1 = typeof fn1 === 'function'? fn1 : function(v) {} fn2 = typeof fn2 === 'function' ? fn2 : function(r) {} if (self.state === 'fulfilled') { return promise2 = new Promise(function(resolve, Reject) {// Put fn1 and fn2 in a try catch. After all, fn1 and fn2 are passed in by the user. Try {var x = fn1(self.data) if (x instanceof Promise) { X.tenn (res => resolve(res), (e) => reject(e))} else {// fn1 Resolve (x)}} catch (e) {reject(e)}})} if (self.state === 'failed') {return promise2 = new Promise(function(resolve, Reject) {try {var x = fn2(self.data) if (x instanceof Promise) { X.tenn (res => resolve(res), (e) => reject(e))} else {// fn1 Resolve (x)}} catch (e) {reject(e)}})} if (self.state === 'pending') {return promise2 = new Promise(function(resolve, reject) { self.resolvedCallbacks.push(function(value){ try { var x = fn1(self.data); If (x instanceof Promise) {// If onResolved returns a Promise, X.tenn (res => resolve(res), (e) => reject(e))} else {// fn1 Through the resolve into then return to the promise of resolve (x)}} the catch (e) {reject (e)}}) self. RejectCallbacks. Push (function (value) { try { var x = fn2(self.data); If (x instanceof Promise) {// If onResolved returns a Promise, X.tenn (res => resolve(res), (e) => reject(e))} else {// fn1 Resolve (x)}} catch (e) {reject(e)}})})}}Copy the code

Run the demo