1. Promise

Promsie is a new approach to js asynchronous logic. Recall how we used to deal with asynchronous JS — using callbacks, but callbacks introduce a whole new set of issues, such as callback hell and trust issues with callbacks. Then developers started looking for new ways to solve the asynchronous problem, and Promise was born. It solves a lot of the problems that callbacks can cause with asynchrony, so let’s take a step-by-step look at what Promise has to offer over callbacks and why it was chosen to handle asynchrony.

2. Promise resolution value

New Promise(function fun(resolve, reject){}); Resolve and reject, which are the resolution functions of promises. Resolve identifies the resolution, reject identifies the rejection, and if you reject, you must pass in a rejection reason.

2.1 Classification of resolution results

Note: High energy ahead, very important:

A promise must be in one of the following three states: pending/fulfilled/rejected. This is a big pity and rejected. I Promise to forget pending decisions. There are only two consequences of a Promise resolution, which intuitively translates to complete and reject. Reject () must be rejected; The result of calling resolve() is not necessarily a pity.

This is because the resolve() function performs different parsing logic depending on the argument it receives, resulting in a different resolution:

  1. If the argument is an instance of a Promise, resolve returns the promise directly. That is to say, if the resolution of this promise is a big pity, the resolution of resolve() will also be fulfilled. If the promise instance resolution is rejected, the resolve of resolve() will also be Rejected, like this:
let pro1 = Promise.reject("Some error happend");
let pro2 = Promise.resolve(pro1);
pro1 === pro2; // true
// Pro1 and Pro2 refer to the same Promise instance, and the resolution is rejected
Copy the code
  1. If the argument is a thenable value (an object or function that has a then method), then thenable is expanded, and if thenable expands to a reject state, then resolve() will also have a reject state.
let obj = {
  then: function(resolve, reject) {
    reject("Some error happend"); }};let pro = Promise.resolve(obj);
pro.then(
  function fulfilled() {
    // Never get here
  },
  function rejected(err) {
    console.log(err); // Some error happend});Copy the code

In the previous example, if resolve or reject is not called in ThEnable, pro will remain pending, and pro. Then will not be called.

  1. If the parameter is any other common value: : is entered directlyfulfilledState and pass this plain value as an argument to the first function in the then method:
let pro = Promise.resolve(1);
pro.then(
  function fulfilled(data) {
    console.log(data); / / 1
  },
  function rejected(err) {
    // Never get here});Copy the code

Resolve and reject, and the differences between them, are clear:

  1. Reject indicates rejected. And reject does not expand on the received value as resolve does. If you pass a Promise to reject, it sets the Promise as a reject, not its underlying value.

  2. This is a big pity. Now I can’t decide whether it is regrettable or rejected. Only after further judgment can I get the result. This may be a pity, or maybe it is the Promise, which is the final Promise. Therefore, it is appropriate that we call resolve rather than completion.

2.2 Invariance of resolution values

An important feature of promises is that once they are made, their resolution values are never changed and can be viewed as many times as needed.

3. Judge the Promise instance

In the Promise world, an important point is how do you determine if a value is a Promise, or if it behaves like a Promise? The first thing you might think of is that since the instance was created by new Promise(), you can tell by instanceof Promise. Unfortunately, this method is not sufficient for checking for a number of reasons:

  1. The Promsie value you get is probably iframe received from another browser window. The Promise of this browser window is different from the Promise of your current window.

  2. Some libraries or frameworks might implement their own promises, and you might get instances that don’t come from the Promise constructor of the current environment.

Solutions:

First, the above instanceof judgment can be used. If it is true, it can be directly judged as a Promise instance. If it is false, then the following “duck type” can be used — if it looks like a duck and quacks like a duck, then it is a duck:

if( p ! = =null &&
  (typeof p === "object" || typeof p === "function") &&
  typeof p.then === "function"
) {
  // Assume this is a thenable (Promise instance)
} else {
  // Not a thenable
}
Copy the code

4. Pain points of resolution

Recall that when we wrote asynchronous code using callbacks, passing a callback to another utility function might cause the following problems:

  1. Call too early;
  2. Call too late;
  3. The callback is not called;
  4. Too few or too many callbacks;
  5. Failed to pass required parameters;
  6. Swallow possible errors or exceptions;
  7. The callback hell

Promise offered effective solutions to all of these problems. Promise addresses the trusted capricious problem of expressing asynchrony through callbacks and the callback hell it can cause.

4.1 Calling Too Early

Call then(…) on Promise The Promise must already be resolved, and the callback passed in to THEN will always be called asynchronously. This is a good way to solve the race condition caused by the callback function sometimes completing synchronously and sometimes asynchronously.

4.2 Late Invocation

After the Promise decision, then registered callbacks are added to the event queue and invoked at the next asynchronous event point. Callbacks registered by a later THEN must wait until the previous THEN callback is executed. Callbacks registered in the previous THEN do not block the execution of callbacks in the subsequent THEN. As a result, the callbacks in then are executed in the order you would expect them to be executed, without the problem of the earlier callbacks being later than the later calls.

4.3 Callback not invoked

For a callback to be called, the Promise must be resolved, so what if the Promise never has a resolution? Even so, Promise offers a solution, a mechanism called races:

function timeoutPromise(delay) {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      reject("Timeout!");
    }, delay);
  });
}

Promise.race([fun(), timeoutPromise(60 * 1000)]).then(
  () = > {
    // Fun () completes in time
  },
  (err) = > {
    // fun() is rejected, or times out
    // Use err to see what the case is});Copy the code

With the above mode, we can guarantee that the call to fun() will always have an output signal, preventing a permanent hang.

4.4 Too few or Too many Calls are Invoked

The correct number of times a callback should be called is 1. If it is called too few times, it is not called at all. The solution was explained above; Too many calls are also easy to handle:

A Promise can only be resolved once, and even if you try to call resolve or reject multiple times, the Promise will accept only the first resolution and ignore all subsequent calls. Once the Promise resolution is complete, the callback from the registrant in then is queued for execution, so the callback registered in THEN is invoked only once.

4.5 Failed to pass required parameters

If you pass a value to resolve or reject, it is guaranteed that once a Promise is resolved, it will be passed to the registered callback in THEN. So if you pass a value to resolve or reject, then callbacks will not receive arguments.

One thing to note is that both resolve and reject accept only one argument, so if you pass in more than one argument, the rest of the arguments after the first one are silently ignored. If you need to pass multiple values, you need to wrap them in an array or object.

4.6 Swallow possible errors or exceptions

When calling a Promise, if you call reject, the Promise is rejected. Rather than just calling reject, if you have a JavaScript error at some point in your code execution, such as TypeError or ReferenceError, that error will be caught and the Promise will be rejected.

This is important because it effectively prevents the possibility of a synchronous response when a method goes wrong, but a differentiated result of an asynchronous response when it succeeds.

4.7 Callback to hell

Promise achieves a good separation of concerns. Promise does not need to focus on what will be done after the resolution before the resolution, nor does the logic after the resolution need to focus on the specific process of the resolution. Combined with then (…). Method, therefore, is a good way to avoid callback hell.

5. The chain flow

5.1 THEN Returns the value

Each time a Promise is called then(…) , it all returns a new Promise, so we can link it together to implement a chaining call to THEN;

5.2 Resolution value of new Promise

The resolution value of the newly returned Promise depends on calling then(…). The implementation result of the callback is a big pity and the registered callback is fulfilled in THEN, which does not represent the resolution value of the returned Promise. Then executes the rejected callback, which does not represent the return Promise value. This depends on the execution and result of the callback in THEN:

This is a big pity, no matter what the callback is performed in THEN, if the callback function executed normally ends without throwing an error, the resolution value of the newly returned Promise will also be fulfilled. On the other hand, if the callback fails or explicitly throws an error, the resolution value of the newly returned Promise is also rejected. As follows:

  • What does the following code print?
Promise.reject("some error")
  .then(null.(err) = > err)
  .then(
    (data) = > {
      console.log("fulfilled: ", data);
    },
    (err) = > {
      console.log("rejected: ", err); });// The code above will print:
// fulfilled: some error
Copy the code
  • What does this piece of code print out?
Promise.resolve(null)
  .then(
    (data) = > data.toString(),
    (err) = > err
  )
  .then(
    (data) = > {
      console.log("fulfilled: ", data);
    },
    (err) = > {
      console.log("rejected: ", err); });// The code above will print:
// rejected: TypeError: Cannot read property 'toString' of null
Copy the code

5.3 Then Callback default

If one of the callback functions passed in to THEN defaults or values null, the Promise will automatically add a default handler that will return any value it receives directly and pass it along the chain until it encounters the display-defined callback function that accepts the value.

Promise.resolve(1)
  .then(null.() = > {})
  .then(
    (data) = > {
      console.log(data); / / 1
    },
    (err) = > {
      console.log(err); });Promise.reject("Some error happend")
  .then(null.null)
  .then(
    (data) = > {
      console.log(data);
    },
    (err) = > {
      console.log(err); // Some error happend});Copy the code

5.4 The callback function returns the Promise processing

If the then success or rejection handler returns a Promise, then the resolve() handler will be expanded, similar to the resolve() handler that received a Promise. His resolution value will be the resolution value of the link Promise returned by the current THEN. So the next linked THEN will wait for the returned Promise to be resolved before it is executed.

function delay(time) {
  return new Promise((resolve, reject) = > {
    setTimeout(resolve, time);
  });
}

Promise.resolve()
  .then(() = > {
    console.log("call step1");
    return delay(2000);
  })
  .then(() = > {
    console.log("call step2");
  });
Copy the code

Does the completion of the callback for the first THEN return a Promise that will not be resolved until 2000ms later? After the Promise resolution is linked, the callback for the second THEN is added to the event queue for execution. This means that call step2 will output at least 2000ms later than Call Step1.

6. Promise static methods and instance methods

This article focuses on the business pain points that Promise addresses and why they are trusted. For the rest of the static and instance methods, you can refer to the MDN documentation in detail, but I won’t repeat myself here. If the above description is not appropriate, welcome to correct; If you find it helpful, please give a thumbs up (^-^). If you want to check out Promsie/A+ specifications for more information, check out Promises/A+ Specifications.

MDN Promise static method

MDN Promise prototype method