This article is written for JavaScript async, Promise syntax understanding, but still not familiar with the MOE new, familiar with JavaScript relations can be dismissed.

First, let’s talk about the original intention of writing this article: During the usual development, I noticed that some friends understand the basic syntax of async and Promise, but their use is limited to the most common cases, and it is easy to turn around when the code or requirements change, so HERE I share my understanding with my teammates.

The most common use of Promise in our project code:

// Pop the mask
httpApi(param)
  .then(res= > {
    // Do some data processing according to res
  })
  .catch(err= > {
    // Error handling logic is sometimes not written
  })
  .finally(() = > {
    // Close the mask
  })
Copy the code

The most common form of async await is also used in HTTP requests:

// Pop the mask
const res = await httpApi(param)
// Do some data processing according to res
// Close the mask
Copy the code

For those of you who know the logic is a little bit missing, you might write it like this:

try {
  // Pop the mask
  const res = await httpApi(param)
  // Do some data processing according to res
} finally {
  // Close the mask
}
Copy the code

(Then he/she realizes that he/she is using async await and that the code is not more concise than in the first example and doesn’t see the need for this syntax.)

When things get a little more complicated, flow control statements are not as easy to read and write. Here’s an example that comes up a lot in normal development:

Example 1: Send request 1 first, and send request 2 after request 1 ends

function f1() {
  // Pop the mask
  httpApi1(param)
    .then(res= > {
      // Do some data processing according to res
      f2() Request 2 is sent after request 1 completes successfully
    })
    .finally(() = > {
      // Close the mask})}function f2() {
  // Pop the mask
  httpApi2(param)
    .then(res= > {
      // ...
    })
    .finally(() = > {
      // Close the mask})}Copy the code

There is a slight problem with this: after calling F2, the mask pops up, but it is immediately turned off by code in the FINALLY block of F1, so the mask closes before the request in F2 ends.

The reason for this is that the writer only understands: “Promise’s then callback uses the logic after success, catch callback uses the logic after failure, and finally callback uses the logic after success or failure.” So he feels that since request 2 is to be sent after request 1 has successfully completed, it should be written in the success callback of request 1. But this fails to understand that promises also serve another purpose: flow control over asynchronous operations.

What is “flow control for asynchronous operations?” For example, if you want to make a weapon in the game, you need several materials to make this weapon. Some of them need to be dropped, and some need to be composed manually. In human terms, this process is actually quite simple. Brush composite materials until composite materials are collected. (Some games require crafting skills up to a certain level) brush your crafting skills up to a certain level and then blend.

As we understand it, this is a linear task. But in practice, it might be “asynchronous”, you swipe a replica, then do a crafting task to gain mastery, then collect crafting materials, then swipe the replica again… So the three tasks of “accumulate duplicate materials”, “accumulate compositing materials” and “develop compositing skill proficiency” are “concurrent”.

And internal details of the three tasks, and appropriate process control logic is needed to put this matter to express clearly, take “save a copy of the material”, opened a copy can not successfully played boss, played the boss material is also likely to drop, may give one or two, may be five or six, so saved material how much need to open the copy is uncertain.

If “hit copy” is a synchronous task:

functionSave copy material () {
  let materialNum = 0;

  while (materialNum < 100) {materialNum += Collect a copy of the material (); }}functionCollect a copy of the material () {
  try {
    letRes = open a ();return res ? res.material || 0 : 0;
  } catch {
    // Failed to type
    return 0; }}Copy the code

But what if grinding is an asynchronous task? It’s a little unreadable to express this process in terms of callbacks:

const materialNeed = 100;
let materialNum = 0;

functionSave copy material () {open one (functionSuccessful callback (res) {
      if (res && res.material) {
        materialNum += res.material;
      }
      if (materialNum < materialNeed) {
        // If the material is not enoughSave copy material (); }},functionFailed callback () {
      if (materialNum < materialNeed) {
        // If the material is not enoughSave copy material (); }}); }Copy the code

It would be nice to visualize this asynchronous process. Promise and async await syntax exist for this purpose.

(The problem with callbacks isn’t just that they don’t represent asynchronous logic in a straightforward way; they also have many drawbacks, as explained in detail in Chapter 2, “Callbacks”, part 2, JavaScript you Don’t Know – Middle Volume.)

Rewrite:

async functionSave copy material () {
  let materialNum = 0;
  while (materialNum < 100) {
    materialNum += awaitCollect a copy of material (); }}async functionCollect a copy of the material () {
  returnOpen a hand ().(res) = > (res ? res.material || 0 : 0))
    .catch(() = > 0);
}
Copy the code

The entire weapons-building process:

async functionBuild weapons () {
  await Promise.all([save copy material (), save composition material (), practice composition skill skill ()]); Synthetic weapons; }Copy the code

If the process of building a weapon were expressed in terms of callbacks, it would be less smooth:

let materialNum = 0;
const materialNeed = 100;
let compositeSkillLevel = 1;
const compositeSkillLevelNeed = 10;
let compositeMaterial = 0;
const compositeMaterialNeed = 200;

functionLiver () {
  if(materialNum < materialNeed &&) {collect a copy material (); }else if(compositeSkillLevel < compositeSkillLevelNeed && Today's compositeSkillLevelNeed) {compositeSkillLevel (compositeSkillLevelNeed) {compositeSkillLevelNeed (compositeSkillLevelNeed); }else if(compositeMaterial < compositeMaterialNeed) {compositeMaterial < compositeMaterialNeed); }}functionCollect a copy of the material () {open one (functionSuccessful callback (res) {
      materialNum += res.material;
      if(All three) {synthetic weapons; }else{liver (); }},functionFailed callback () {liver (); }); }functionBrush once composition skill Proficiency () {do a composition task (functionSuccessful callback (res) {
      compositeSkillLevel = res.currentSkillLevel;
      if(All three) {synthetic weapons; }else{liver (); }},functionFailed callback () {liver (); }); }functionCollect synthetic materials against monsters () {spawn a wave of monsters (functionSuccessful callback (res) {
      compositeMaterial += res.material;
      if(All three) {synthetic weapons; }else{liver (); }},functionFailed callback () {liver (); }); }// Properly reusing code can make asynchronous processes represented by callbacks relatively concise, but still painful to read.

Copy the code

So the Promise syntax, the async await syntax, plays an important role in expressing an asynchronous process as if it were a synchronous operation.

Without going into the details of async await, after use the code looks almost the same as the synchronous version. (MDN description of async await)

What about Promise’s role in flow control? Doesn’t Promise also accept success and failure callbacks, and how do you use it to make your flow “smooth” and readable?

Then, catch, and finally methods on a Promise instance return a new Promise instance, and then, catch, and finally methods on a new Promise instance return a new Promise instance. That is to say, the results of an asynchronous task (success or failure is a result) do some processing, the preceding task + this pile of processing logic, can be seen as a “bigger task”, the “bigger task” will also have its completion, whether success or failure, we still can continue to write logic based on the complete case downwards. In this way, the code logic is not deeper and deeper, but longer and longer, like a chain to go down.

Here’s an example:

A Promise can be thought of as a task.

Let’s say I have task P1

p1.then(() = > {
  // bala bala
});

// ------------- is equivalent to: --------------

await p1
// bala bala
Copy the code
p1.then((res) = > {
  // Do some operations on res
});

// ------------- is equivalent to: --------------

let res = await p1;
// Do some operations on res
Copy the code

Promise. Then returns a new Promise.

1. Perform task P1

2. If it is successful, do some processing. If the then method fails, do something else.

The new Promise represents the implementation of lines 1 and 2 together. That is, the first line counts as a task, but you can also combine lines 1 and 2 as a larger task, and the promise. then method returns the execution of that “larger task.”

p1.then((res1) = > {
  // Do some operations on res1
  returnA value; }) .then((res2) = > {
  // Do some operations on res2
});

// ------------- is equivalent to: --------------

let res1 = await p1;
let res2 = awaitDo some operations on RES1 (RES1);// Do some operations on res2
Copy the code
p1.then((res1) = > {
  // Do some operations on res1
})
.catch((err) = > {
  // Error handling. The err here could be thrown by P1, or it could be thrown by the part below P1
});

// ------------- is equivalent to: --------------

try {
  let res1 = await p1;
  // Do some operations on res1
} catch (err) {
  // Error handling, where err may be thrown from the first line of the try block, or from the second line of the try block
}
Copy the code
p1.catch((err) = > {
  // Error handling
})
.then((res1) = > {
  // Do some operations on res1
});

// ------------- is equivalent to: --------------

let res1;
try {
  res1 = await p1;
} catch (err) {
  // Error handling
}
// Do some operations on res1
Copy the code

If a promise is followed by a “then catch”, the code in the “then” is the code that follows the previous task. See the code in the catch: wrap the first parts of the promise chain into a try block that corresponds to the catch block.

(Finally is a long story, so let me skip it for a moment.)

Let’s go back to the common “send request 1 first and send request 2 after the end of request 1” in the development mentioned above. If you just want the mask to behave normally, change finally in F1 to catch to make the mask behave normally

function f1() {
  // Pop the mask
  httpApi1(param)
    .then((res) = > {
      // Do some data processing according to res
      f2();
    })
    .catch(() = > {
      // Close the mask
    });
}

function f2() {
  // Pop the mask
  httpApi2(param)
    .then((res) = > {
      // ...
    })
    .finally(() = > {
      // Close the mask
    });
}
Copy the code

But this doesn’t work as well as Promise, because logically f1 and F2 are two steps on the same level.

function f1() {
  // Pop the mask
  return httpApi1(param)
    .then((res) = > {
      // Do some data processing according to res
    })
    .finally(() = > {
      // Close the mask
    });
}

function f2() {
  // Pop the mask
  return httpApi2(param)
    .then((res) = > {
      // ...
    })
    .finally(() = > {
      // Close the mask
    });
}

f1().then(f2); // Instead of nesting them, they become one link after another on the Promise chain
Copy the code

Should async await and Promise be unified when used in projects?

I don’t think so, just remember that a Promise chain can be considered an asynchronous task, whether it has only one or many rings. An asynchronous task, on the other hand, can always await the result of the task before executing the following statement. Once you understand these two things, you can use them together in everyday development without getting confused.

Here’s another example we see in development:

function f() {
  return new Promise((resolve, reject) = >{a API (). Then ((res) = > {
        resolve(res.xxx);
      })

      .catch(() = > {
        // bala bala
        reject();
      });
  });
}
Copy the code

The writer wants this method to return a Promise. He or she wants this method to represent an asynchronous task, but the then catch method that forgets a Promise always returns a new Promise. A Promise chain can itself be considered an asynchronous task.

// Don't waste promises that the Promise API itself will return
function f() {
  returnSome apis (). Then ((res) = > {
      return res.xxx;
    })
    .catch(() = > {
      // bala bala
      throw new Error(a); }); }Copy the code

This is all I can share with you about the Promise asynchronous flow control statement.

In addition, according to my daily development experience, there is another small point to pay attention to: the result value of an asynchronous task is to await the result of an asynchronous task. It is easy to fall into a trap without understanding this. That’s a small topic for another time.

Finally, I wish you all (and I count a few) in the usual development of clear-eyed, waist and horse unity, process control grasp in place, 0 bug!