Why do WE need Promise

Javascript is a single-threaded language, so in the early days we were dealing with asynchronous scenarios mostly through callback functions.

For example, sending an Ajax request in a browser is a common asynchronous scenario where the request is sent and the server responds some time before we get the result. If we want to perform an operation after the asynchrony ends, the only way to do so is through a callback function.

 var dynamicFunc = function(cb) {
  setTimeout(function() {
    cb();
  }, 1000);
}
dynamicFunc(function() {console.log(123)});
Copy the code

In the example above, dynamicFunc is an asynchronous function whose setTimeout calls the cb function after 1s. Finally, after 1s, the result 123 will be printed.

Similarly, if there is something that needs to be output at the end of the asynchronous function, multiple asynchronous functions need to be nested, which is not good for subsequent maintenance.

 setTimeout(function() {
  console.log(123);
  setTimeout(function() {
      console.log(321);
  	// ...
  }, 2000);
}, 1000);
Copy the code

In order to make callback functions more elegantly invoked, IN ES6 JS created a new specification called Promise that makes asynchronous operations almost “synchronous.”

Promise based

In an advanced browser environment that supports ES6, we can construct a Promise instance by using New Promise().

The constructor takes a function that takes two arguments, resolve and reject, indicating that we need to change the state of the current instance to complete or reject.

 function promise1() {
  return new Promise(function(resolve, reject) {
      // Define asynchronous content
      setTimeout(function() {
        console.log('Output after 1s');
        // When the output is complete, call the resolve function passed in by the function, mark the promise instance as completed, and the current promise string continues execution
        resolve();
      }, 1000);
}); }
Copy the code
 function promise3() {
  return new Promise(function(resolve, reject) {
  var random = Math.random() * 10; // Pick a random number from 1 to 10
  setTimeout(function() {
        if (random >= 5) {
          resolve(random);
        } else{ reject(random); }},1000);
}); }
var onResolve = function(val) { console.log('Done: The output number is', val);
};
var onReject = function(val) { console.log('Rejected: the output number is', val);
}
 // Promise's then can also accept two functions, the first of which is resolved and the second reject
promise3().then(onResolve, onReject);
// You can also use the.catch method to intercept promises when the state becomes rejected
promise3().catch(onReject).then(onResolve);
// It is also possible to intercept a rejected promise using a try catch
try {
  promise3().then(onResolve);
} catch (e) {
  onReject(e);
}
Copy the code

This example uses three ways to intercept promises that eventually become “rejected” by using the second argument to then to catch any exceptions thrown by the promise. Using a try catch to intercept exceptions thrown by promises in a block of code, we can also see that when we call resolve and Reject when we change the promise state, we can also pass arguments to the next function executed in then. In this example, we pass the randomly generated number to resolve and reject, so that we get the value when we execute the function in then.

  1. A promise can have three states: “In progress,” “Completed,” and “rejected.” An ongoing state can be changed to completed or rejected, and a changed state cannot be changed (for example, from completed to rejected).
  2. The Promise constructor in ES6, after we build it, needs to pass in a function that takes two function arguments, changes the current Promise state to “completed” after the first argument, and “rejected” after the second argument.
  3. The.then method allows you to continue executing the next function or promise when the previous promise has reached completion. You pass in an initial value to the next function or promise when you pass in both resolve and reject.
  4. A rejected promise can then be caught using either the.catch method or the second argument to the.then method or a try catch.

How do I encapsulate asynchronous operations as Promises

We can encapsulate any function that accepts a callback as a promise. Here are a few simple examples.

 / / function
function dynamicFunc(cb) {
	setTimeout(function() { 
    	console.log('1s after display '); cb();
	}, 1000); 
}
var callback = function() { 
	console.log('Log after asynchrony ends');
}
// execute as a callback passed in
dynamicFunc(callback);
Copy the code

The above example is the most traditional, passing in a callback to execute the function after the asynchrony ends. We can turn this asynchronous function into a promise by encapsulating the promise.

function dynamicFuncAsync() {
  return new Promise(function(resolve) {
    setTimeout(function() { 
      console.log('1s after display '); resolve();
    }); 
  });
}
var callback = function() { 
  console.log('Log after asynchrony ends');
}
dynamicFuncAsync().then(function() { callback(); });
Copy the code

As another example, sending ajax requests can also be wrapped:

 function ajax(url, success, fail) {
  var client = new XMLHttpRequest();
  client.open("GET", url);
  client.onreadystatechange = function() {
    if (this.readyState ! = =4) {
      return;
    }
    if (this.status === 200) {
      success(this.response);
    } else {
      fail(new Error(this.statusText)); }}; client.send(); }; ajax('/ajax.json'.function() {console.log('success')}, function() {console.log('failure')});
Copy the code

As you can see, calling ajax methods requires passing in the success and fail callback functions. Instead of passing in the callback number, we can use the chain by encapsulating the promise and changing the state of the current promise where the callback function was executed.

 function ajaxAsync(url) {
  return new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = function() {
      if (this.readyState ! = =4) {
        return;
      }
      if (this.status === 200) {
      	resolve(this.response);
      } else {
        reject(new Error(this.statusText)); }}; client.send(); }); }; ajaxAsync('/ajax.json')
  .catch(function() {
  	console.log('failure'); 
  })
  .then(function() { 
  	console.log('success');
  })
Copy the code
  1. We can easily change any function or asynchronous function to promise, especially asynchronous function, after changing promise, chain call can be made, improving readability.
  2. Changing the async with the callback function to a PROMISE is also as simple as internally instantiating the promise and executing the corresponding function that changes the promise state where the callback function was originally executed.

Promise specification interpretation

Any object or function that meets the promise specification can become A Promise, promise A plus

We learned how to create a promise and how to use it. We learned how to modify the callback function to a promise. In this section, we go through the Promise A+ specification in detail to understand the details in the process of using promise from the specification level.

The state of the promise

The current state of a Promise must be one of the following three states: Pending, Fulfilled and Rejected.

In the wait state, a promise must satisfy the following conditions: It can become “completed” or “rejected”

When a promise is completed, the following conditions must be met :1. It cannot be migrated to any other state; 2. Must have an immutable value

When a promise is rejected, the following conditions must be met :1. It cannot be migrated to any other state. 2. There must be an immutable cause

There must be a then method

A promise must provide a THEN method to access its current value and reason.

Promise’s then method accepts two arguments: Then (onFulfilled, onRejected) they are all optional parameters. At the same time, they are all functions. If onFulfilled or onRejected is not a function, then we need to ignore them.

If ondepressing is a function

When the promise execution ends, it must be invoked, and its first argument is the result of the promise. It cannot be invoked more than once before the promise execution endsCopy the code

If onRejected is a function

When a promise is rejected it must be invoked, and the reason its first argument is a promise is that it cannot be invoked more than once before a promise is rejectedCopy the code

OnFulfilled or onRejected must not be called until the execution context stack contains only platform code

Ondepressing and onRejected must be called as a normal function (i.e. not instantiated, so that inside the function this points to the window in non-strict mode).

The then method can be called multiple times by the same promise

When the Promise is successfully implemented, all onFulfilled shall be called back in turn according to its registration order. When the Promise is rejected, all onFulfilled shall be called back in turn according to its registration orderCopy the code

The then method must return a promise object promise2 = promise1. Then (onFulfilled, onRejected)

Promise 2 will enter onFulfilled state as long as onFulfilled or onRejected returns a value of X

If ondepressing or onRejected throws an exception E, promise2 must reject the implementation and return the rejection cause E

If ondepressing is not a function and the state of promise1 becomes completed, promise2 must perform successfully and return the same value

If onRejected is not a function and the status of promise1 becomes rejected, promise2 must perform a reject callback and return the same data

var promise1 = new Promise((resolve, reject) = >{reject(); }); promise1 .then(null.function() {
    return 123;
  })
  .then(null.null)
  .then(null.null)
  .then(
    () = > {
    console.log('Promise2 completed');
    },
    () = > {
    console.log('Promise2 has been rejected');
    });
    
    / / promise2 is complete
Copy the code

Promise solution

Promise resolution is an abstract operation that takes a Promise and a value, which we express as [[Resolve]] (Promise, x).

 promise.then(function(x) { 
  console.log("Will execute this function and pass in the value of the x variable.", x);
 });
Copy the code
  • If X has a then method and looks like a Promise, the resolver tries to make the Promise accept X and enforce the Promise with the value of x.

  • If a promise and x refer to the same object, use TypeError as evidence for refusing to implement the promise if x is a promise

    • If X is in wait state, the promise needs to remain in wait state until x is executed or rejected

    • If x is in the execution state, execute the promise with the same value

    • If X is in the reject state, reject the promise with the same grounds

     var promise1 = function() {
      return new Promise(function(resolve) {
        setTimeout(function() {
          console.log(1);
          resolve();
    }, 1000)}); }var promise2 = function() {
      return new Promise(function(resolve) {
        setTimeout(function() {
          console.log(2);
          resolve();
    }, 2000); });
    }
    promise1()
      .then(function() {
        return promise2(); // An instance of promise is returned
      })
      .then(function() {
        console.log('Done')},function() {
        console.log('Rejected')});Copy the code
    • If x is Object or function(uncommon)
      • Try executing x. teng first

      • If an error e is thrown when taking the value x. teng, reject the promise based on e

      • If then is a function, x is called as the function’s scope this. Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise:

        • If resolvePromise is called with the value y, run [[Resolve]](promise, y)
        • If rejectPromise is invoked with argument r, reject the promise with argument r
        • If both resolvePromise and rejectPromise are called, or if the same argument is called more than once, the first call is preferred and the rest is ignored if the then method is called and exception E is thrown
        • If either the resolvePromise or rejectPromise has already been invoked, it is ignored
        • Otherwise, reject the promise based on e
      • If then is not a function, change the promise to the completed state, taking x as an argument

  • If x is not an object or function, change the promise to a completed state with x as an argument (important and common)

Static methods on the Promise constructor

Promise.resolve

Returns a Promise instance with its state set to completed and its result passed in as the value of the Promise instance

 var promise = Promise.resolve(123);
promise
  .then(function(val) {
	console.log('Done', val); 
  });
// 123 completed
Copy the code

Similarly, the promise.resolve argument can handle objects, functions, and so on in the same way as described in the specification above.

Promise.reject

var promise = Promise.reject(123);
promise
  .then(null.function(val) {
	console.log('Rejected', val); 
  });
// 123 has been rejected
Copy the code

Promise.all

Return a Promise instance that takes an array of multiple Promise instances and goes into the completed state when all the promise instances are in the completed state, or into the rejected state otherwise.

var promise1 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(1);
      resolve();
	}, 1000)})}var promise2 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(2);
      resolve();
}, 2000); });
}
Promise.all([promise1(), promise2()])
  .then(function() {
	console.log('All promises have been fulfilled'); 
  });

Copy the code

Note that multiple promises are made at the same time, i.e. in the example above, after waiting 1s to print 1, waiting another 1s to print 2 and “All promises are completed.”

Promise.race

Return a Promise instance that takes an array of multiple Promise instances. When a promise instance’s state changes, it goes into that state and cannot be changed. Here, all promise instances are competing, and only the first promise to enter the changed state is selected.

 var promise1 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(1);
      resolve(1);
}, 1000)}); }var promise2 = function() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      console.log(2);
      resolve(2);
}, 2000); });
}
Promise.race([promise1(), promise2()])
  .then(function(val) {
	console.log('There is a promise state that has changed', val); 
  });
Copy the code

Fulfill a promise

// Full implementation of Promise
class Promise {
  callbacks = [];
  state = 'pending';// Add state
  value = null;// Save the result
  constructor(fn) {
    fn(this._resolve.bind(this), this._reject.bind(this));
  }
  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) = > {
      this._handle({
        onFulfilled: onFulfilled || null.onRejected: onRejected || null.resolve: resolve,
        reject: reject
      });
    });
  }
  catch(onError) {
    return this.then(null, onError);
  }
  finally(onDone) {
    if (typeofonDone ! = ='function') return this.then();

    let Promise = this.constructor;
    return this.then(
      value= > Promise.resolve(onDone()).then(() = > value),
      reason= > Promise.resolve(onDone()).then(() = > { throw reason })
    );
  }
  static resolve(value) {
    if (value && value instanceof Promise) {
      return value;
    } else if (value && typeof value === 'object' && typeof value.then === 'function') {
      let then = value.then;
      return new Promise(resolve= > {
        then(resolve);
      });

    } else if (value) {
      return new Promise(resolve= > resolve(value));
    } else {
      return new Promise(resolve= >resolve()); }}static reject(value) {
    if (value && typeof value === 'object' && typeof value.then === 'function') {
      let then = value.then;
      return new Promise((resolve, reject) = > {
        then(reject);
      });

    } else {
      return new Promise((resolve, reject) = >reject(value)); }}static all(promises) {
    return new Promise((resolve, reject) = > {
      let fulfilledCount = 0
      const itemNum = promises.length
      const rets = Array.from({ length: itemNum })
      promises.forEach((promise, index) = > {
        Promise.resolve(promise).then(result= > {
          fulfilledCount++;
          rets[index] = result;
          if(fulfilledCount === itemNum) { resolve(rets); }},reason= >reject(reason)); })})}static race(promises) {
    return new Promise(function (resolve, reject) {
      for (let i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(function (value) {
          return resolve(value)
        }, function (reason) {
          return reject(reason)
        })
      }
    })
  }
  _handle(callback) {
    if (this.state === 'pending') {
      this.callbacks.push(callback);
      return;
    }

    let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;

    if(! cb) {// If nothing is passed in then
      cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
      cb(this.value);
      return;
    }

    let ret;

    try {
      ret = cb(this.value);
      cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
    } catch (error) {
      ret = error;
      cb = callback.reject
    } finally{ cb(ret); }}_resolve(value) {
    if(this.state ! = ='pending') return
    if (value && (typeof value === 'object' || typeof value === 'function')) {
      var then = value.then;
      if (typeof then === 'function') {
        then.call(value, this._resolve.bind(this), this._reject.bind(this));
        return; }}this.state = 'fulfilled';// Change the state
    this.value = value;// Save the result
    this.callbacks.forEach(callback= > this._handle(callback));
  }
  _reject(error) {
    if(this.state ! = ='pending') return
    this.state = 'rejected';
    this.value = error;
    this.callbacks.forEach(callback= > this._handle(callback)); }}Copy the code

Some implementation questions for Promise

1.


Promise.resolve()
  .then(() = > {
    return new Error('error!!! ');
  })
  .then(res= > {
    console.log('then:' + res);
  })
  .catch(err= > {
    console.log('catch:' + err);
  })

Copy the code

Then :Error: Error!!

2,

let promise = new Promise((resolve, reject) = > {
  resolve('success1');
  reject('error');
  resolve('success2');
})

promise
  .then((res) = > {
    console.log('then:' + res);
  })
  .catch((err) = > {
    console.log('catch:' + err);
  });

Copy the code

Output: THEN: Success1

3,

let promise = new Promise((resolve, reject) = > {
  console.log(1);
  resolve();
  console.log(2);
})

promise.then(() = > {
  console.log(3);
})
console.log(4);

Copy the code

Output: 1, 2, 4, 3

The executors inside the Promise constructor are synchronized code, and the.then registered callback is a microtask, so synchronization code 1 is printed first. Resolve () does not prevent subsequent synchronization because there is no return statement. Then the microtask is added to the microtask queue, and the synchronization code 2 is printed, then the synchronization code 4 is printed first, and finally the task element in the microtask queue is printed, so the print result is 1, 2, 4, 3.

4,

let p1 = new Promise((resolve, reject) = > {
  reject(42);
});

p1.catch((function (value) {
  console.log(value);
  return value + 1;
})).then(function (value) {
  console.log(value);
});

Copy the code

The following output is displayed: 42 43

5,

let p1 = new Promise((resolve, reject) = > {
  resolve(42);
});

let p2 = new Promise((resolve, reject) = > {
  reject(new Error('TypeError!!! '));
});

p1.then(function (value) {
  console.log(value);
  return p2;
}).then(function (value) {
  console.log(value);
}, function (err) {
  console.log(err);
})

Copy the code

42 Error: TypeError!!

6,

setTimeout(() = > {
  console.log('timer1');
  Promise.resolve().then(() = > {
    console.log('promise1'); })})Promise.resolve().then(() = > {
  console.log('promise2');
  setTimeout(() = > {
    console.log('timer2'); })})Copy the code

Promise2 timer1; promise1 timer2

7,

Promise.resolve()
  .then(() = > { // First then
    Promise.resolve().then(() = > {
      console.log(1);
    }).then(() = > {
      console.log(2);
    })
  })
  .then(() = > { // Outer second then
    console.log(3);
  })

Copy the code

Output: 1 3 2

Put the first then on the microtask list and wait for the second then to return a promise. The same logic applies to the first THEN.

Eight,

async function async1() {
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2 end');
}
async1();
console.log(10);

Copy the code

Async1 end 10 AsynC2 end

9,

async function async1() {
  try {
    await async2();
  } catch (err) {
    console.log('async1 end');
    console.log(err); }}async function async2() {
  console.log('async2 end');
  return Promise.reject(new Error('error!!! '));
}
async1();
console.log(10);


Copy the code

Async2 end 10 AsynC1 end error!!

10,

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');


Copy the code

Script start async1 start async2 promise1 Script end asynC1 end promise2 setTimeout

11,

let a;

const b = new Promise((resolve, reject) = > {
  console.log('promise1');
  resolve();
}).then(() = > {
  console.log('promise2');
}).then(() = > {
  console.log('promise3');
}).then(() = > {
  console.log('promise4');
});

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a;
  resolve(true);
  console.log('after2');
})

console.log('end');

Copy the code

Promise1 undefined end promise2 promise3 promise4 Promise{pending} after1