• Last update: 2020-12-14

.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}

To better understand Async/Await, we need to understand the following two points:

  • synchronous
  • asynchronous

background

First of all, js is a single-threaded repeat (three times), the so-called single thread, popular speak be, one track-minded (metaphor for much too much, ha ha) code is the line go down (the so-called synchronization), if not performed, stupid waiting for (is it like in love on the side of the road waiting for her/his, you pretend to new objects, Aha ha ha ha, naughty once very happy), or give 🌰 :

// chrome 81
function test() {
  let d = Date.now();
  for (let i = 0; i < 1e8; i++) {}
  console.log(Date.now() - d); / / around 62 ms - 90 ms
}

function test1() {
  let d = Date.now();

  console.log(Date.now() - d); / / 0
}

test();
test1();
Copy the code

The above is just a for loop, but in practical applications, there will be a large number of network requests, its response time is uncertain, in this case also need to wait? Obviously not, so JS design asynchronous, namely initiated network request (such as IO operation, timer), due to the need to wait for the server response, first ignore, but to do other things, such as the request to return the result of the time again (that is asynchronous). So how do you implement asynchrony? Callback: Callback callback: callback callback

// Network request
$.ajax({
  url: 'http://xxx'.success: function(res) {
    console.log(res); }});Copy the code

Success is passed as a function that does not execute immediately, but waits for the request to succeed.

/ / IO operations
const fs = require('fs');

fs.rename('Old file.txt'.'New file.txt'.err= > {
  if (err) throw err;
  console.log('Rename done');
});
Copy the code

Similar to network requests, the third argument is not executed until the IO operation has a result (successful or unsuccessful) :(err)=>{}

From the above we can see that the core of asynchrony is the callback hook, which passes cb as an argument to the asynchrony execution function, and then fires CB when it has a result. To learn more, check out the Event-loop mechanism.

As for how async/await comes into being, before ES6, most JS projects would have code like this:

ajax1(url, () = > {
  // do something 1
  ajax2(url, () = > {
    // do something 2
    ajax3(url, () = > {
      // do something 3
      // ...
    });
  });
});
Copy the code

This nesting of functions and a large number of callback functions make the code difficult to read and unintuitive, so it is vividly called callback hell. Therefore, in order to write more commonly, ES6 + has successively appeared Promise, Generator, Async/await. Strive for simplicity (flat) and readability (more elegant, more concise).

= = = = = = = = = = = = = = = = = = = = = = = = = I’m line = = = = = = = = = = = = = = = = = = = = = = = = = =

The above is just to prepare, let’s go to the topic 👇 and begin to say the main character: async/await

= = = = = = = = = = = = = = = = = = = = = = = = = I’m line = = = = = = = = = = = = = = = = = = = = = = = = = =

Async /await is a set of asynchronous processing schemes that refer to Generator encapsulation and can be understood as Generator syntactic sugar.

Therefore, to understand async/await, we have to talk about Generator. (The concept of coroutines was introduced into JS for the first time. It is a subset of coroutines.

The Generator returns an Iterator,

So I have to talk about Iterator,

Both Iterator and Generator are coroutines.

Finally, the source: coroutines

coroutines

Wiki: Coroutines (English: Coroutine) are components of computer programs that promote cooperative multitasking subroutines that allow execution to be suspended and resumed. Coroutines are more general and flexible than subroutines, but are not as widely used in practice. Coroutines are better suited for implementing familiar program components such as cooperative multitasking, exception handling, event loops, iterators, infinite lists, and pipes

Coroutines can yield to other coroutines, and each time the coroutine is invoked, the execution will continue from where the last yield returned. The relationship between coroutines that yield to each other is symmetric and equal, not between caller and caller

Coroutines are products of extreme performance and elegant code structures where calls between coroutines are logically controlled and sequentially determined

Coroutines are more lightweight than threads. They are language-level constructs that can be viewed as a form of control flow that executes in memory without the overhead of switching between threads. You can think of coroutines as tasks running on threads, where multiple coroutines can exist on a thread, but only one coroutine can be executed on a thread at a time.

The concept of coroutines was developed earlier in the single-cpu scenario. It provides a suspend and resume interface to realize the concurrent function of interprocessing multiple tasks on a single CPU.

In essence, the switch of different task stacks is added on the basis of a thread. By suspending and resuming different task stacks, the code fragments running alternately in the thread can realize the function of concurrency.

In fact, it can be seen from this that “calls between coroutines are logically controllable and sequentially determined”.

So how to understand the js coroutine?

  • – Js highway is only one lane (main thread), but there are many lanes (helper thread) that can be merged into the traffic flow (asynchronous task after completion of the task back to the main thread task queue)
  • generator– Js highway becomes multi-lane (coroutine implementation), but only cars in one lane can drive at a time (still single threaded), but can change lanes freely (hand over control)

Coroutines implementation

Here is a simple example of the usefulness of coroutines. Suppose such a producer-consumer relationship, where one coroutine produces products and adds them to the queue, and the other coroutine takes products out of the queue and consumes them. The pseudo-code is shown as follows:

Var q := Create new queue coroutine producer loop while q is not full create new queue coroutine producer loop while Q is not full create new queue coroutine consumer loop while Q is not empty remove some products from Q Yield these products to the producerCopy the code

V8 implementation source code: JS-generator, Run-time generator

Compile simulation Implementation (ES5) : ReGenerator

With that said, let’s pretend you understand what a coroutine is. What about iterators

Iterator

Let’s take a look at the Iterator first (similar to a one-way list) :

  • Creates a pointer object that points to the start of the current data structure
  • The first time a pointer object is callednextMethod to point to the first member of the data structure
  • The second call to the pointer objectnextMethod to point to the second member of the data structure
  • Constantly calling the pointer objectnextMethod until it points to the end of the data structure

For an object to become iterable, it must implement the @@iterator method. That is, the object (or an object in its prototype chain) must have a property named symbol. iterator. String, Array, TypedArray, Map, and Set) can be accessed via the constant symbol.iterator:

attribute value
[Symbol.iterator]: A nonparametric function that returns an object conforming to the iterator protocol

When an object needs to be iterated over (such as starting for a for.. Of), whose @@iterator method is called with no arguments and returns an iterator used to get the value in the iteration

Iterator protocol: Produces a finite or infinite sequence of values, with a default return value when all values have been iterated over

An object is considered an iterator only if it meets the following conditions:

It implements a next() method that must return an object with two necessary attributes:

  • done(bool)
    • True: The iterator has exceeded the number of iterations. In this case, the value of value can be omitted
    • False if the iterator can produce the next value in the sequence. This is equivalent to not specifying the done attribute
  • valueAny JavaScript value returned by the iterator. Done can be omitted when true

According to the above rules, let’s customize a simple iterator:

const getRawType = (target) = > Object.prototype.toString.call(target).slice(8, -1);

const __createArrayIterable = (arr) = > {
  if (typeof Symbol! = ='function' || !Symbol.iterator) return {};
  if(getRawType(arr) ! = ='Array') throw new Error('it must be Array');
  const iterable = {};
  iterable[Symbol.iterator] = () = > {
    arr.length++;
    const iterator = {
      next: () = > ({ value: arr.shift(), done: arr.length <= 0})}return iterator;
  };
  return iterable;
};

const itable = __createArrayIterable(['one month'.'myths']);
const it = itable[Symbol.iterator]();

console.log(it.next()); // {value: "", done: false}
console.log(it.next()); // {value: "myth ", done: false}
console.log(it.next()); // {value: undefined, done: true }
Copy the code

We can also customize an iterable:

Object.prototype[Symbol.iterator] = function () {
  const items = Object.entries(this);
  items.length++;
  return {
    next: () = > ({ value: items.shift(), done: items.length <= 0}}})// or
Object.prototype[Symbol.iterator] = function* () {
  const items = Object.entries(this);
  for (const item of items) {
    yielditem; }}const obj = { name: 'amap'.bu: 'sharetrip'}
for (let value of obj) {
  console.log(value);
}
// ["name", "amap"]
// ["bu", "sharetrip"]
// or
console.log([...obj]); // [["name", "amap"], ["bu", "sharetrip"]]
Copy the code

💡 How do methods like for map forEach traverse an array?

Refer to the answer
const getIterator = (iteratorable) => iteratorable[Symbol.iterator](); ,1,2,3,4,5 const arr = [0]; const iterator = getIterator(arr); while(true){ const obj = iterator.next(); if(obj.done){ break; } console.log(obj.value); }Copy the code

Now that you know about iterators, you can take a closer look at generators

Generator

Generator: The Generator object is returned by the Generator function, complies with the iterable and iterator protocols, is both an iterator and an iterable, and can call the next method, but it is not a function, let alone a constructor

GeneratorFunction:

function* name([param[, param[, … param]]]) { statements }

  • Name: indicates the function name
  • Param: parameter
  • Statements: JS statement

Calling a generator function does not execute its statements immediately. Instead, it returns an iterator object of the generator. When the iterator’s next() method is first called, the iterator’s statements are executed until the first yield occurs (leaving execution suspended). Yield follows the value to be returned by the iterator. Or if yield* (with an asterisk) is used to transfer execution to another generator function (the current generator pauses), the next() (restart, wake up) method is called, and if a parameter is passed, that parameter will be the return value of the last yield statement executed, for example:

function* another() {
  yield The Myth of the Man-month;
}

function* gen() {
  yield* another(); // Transfer of executive authority
  const a = yield 'hello';
  const b = yield a; // a='world' is the lvalue assigned by the next('world') pass to the previous yidle 'hello'
  yield b; / / b =! Is the next ('! ') is assigned to the lvalue of the previous yidle A
}

const g = gen();
g.next(); // {value: "", done: false}
g.next(); // {value: "hello", done: false}
g.next('world'); // {value: "world", done: false} assigns 'world' to the lvalue of the previous yield 'hello', i.e. a='world',
g.next('! '); // {value: "!" , done: false} will '! The lvalue assigned to the previous yield a, which executes b='! ', return b
g.next(); // {value: undefined, done: false}
Copy the code

What does Generator have to do with callback, you might ask, and how does asynchrony work? There is no relationship between the two, we just force it in some way so that the Generator handles asynchrony

To summarize the nature of the Generator, pause, which causes the program to execute at a specified point (yield), then start (next), then stop (yield), then start (next), and this pause makes it easy to associate with asynchronous operations because we are dealing with asynchrony: Start asynchronous processing (network interceding, IO operations), and then pause until the processing is complete. Note, however, that JS is single threaded (again three times), asynchronous or asynchronous, callback or callback, and does not change with the Generator

Let’s write asynchronous code with Generator + Promise:

const gen = function* () {
  const res1 = yield Promise.resolve({a: 1});
  const res2 = yield Promise.resolve({b: 2});
};

const g = gen();

const g1 = g.next();

console.log('g1:', g1);

g1.value
  .then(res1= > {
    console.log('res1:', res1);
    const g2 = g.next(res1);
    console.log('g2:', g2);
    g2.value
      .then(res2= > {
        console.log('res2:', res2);
        g.next(res2);
      })
      .catch(err2= > {
        console.log(err2);
      });
  })
  .catch(err1= > {
    console.log(err1);
  });
// g1: { value: Promise { <pending> }, done: false }
// res1: { "a": 1 }
// g2: { value: Promise { <pending> }, done: false }
// res2: { "b": 2 }
Copy the code

As you can see, you still need to manually add the.then layers of callback, but since the next() method returns an object {value: XXX,done: True /false} so we can simplify it by writing an autoexecutor:

function run(gen) {
  const g = gen();

  function next(data) {
    const res = g.next(data);
    // Deep recursion, 'next' calls itself as soon as the 'Generator' function has not reached the last step
    if (res.done) return res.value;
    res.value.then(function(data) {
      next(data);
    });
  }

  next();
}

run(function* () {
  const res1 = yield Promise.resolve({a: 1});
  console.log(res1);
  // { "a": 1 }
  const res2 = yield Promise.resolve({b: 2});
  console.log(res2);
  // { "b": 2 }
});
Copy the code

Async /await = async/await = async/await Promise and callback relationships, thunk function, co library, Google if you’re interested, ruanyifeng’s es6 tips are great, I always check them out.)

💡 analyze the following log output?

function* gen() {
  const ask1 = yield "2 plus 2 equals?";
  console.log(ask1);

  const ask2 = yield "3 times 3 is what?"
  console.log(ask2);
}

const generator = gen();

console.log( generator.next().value );

console.log( generator.next(4).value );

console.log( generator.next(9).done );
Copy the code
Refer to the answer
// 2 + 2 =? // 4 // 3 + 3 =? // 6 // trueCopy the code

Async/Await

First of all, async/await is the syntactic sugar of Generator. The first sentence below the segmentation line has been described above. Let’s see the comparison of the two.

// Generator
run(function* () {
  const res1 = yield Promise.resolve({a: 1});
  console.log(res1);

  const res2 = yield Promise.resolve({b: 2});
  console.log(res2);
});

// async/await
const aa = async() = > {const res1 = await Promise.resolve({a: 1});
  console.log(res1);

  const res2 = await Promise.resolve({b: 2});
  console.log(res2);

  return 'done'; }const res = aa();
Copy the code

As you can see, async function replaces function*, await replaces yield, and there is no need to write an autoexecutor run by hand

Now let’s look at the features of async/await:

  • whenawaitIf it is followed by a Promise object, it is executed asynchronously, and other types of data are executed synchronously
  • performconst res = aa();The return is still a Promise object in the code abovereturn 'done';Will be directly underthenFunction receives
res.then(data= > {
  console.log(data); // done
});
Copy the code

Finally, let’s sum up:

Advantages:

  • Built-in actuators: Built-in actuators
  • Better semantics than asterisks andyieldThe semantics are clearer
  • Wider applicability:awaitAfter the command, you can followPromiseObject and primitive type values (this is equivalent to synchronizing operations)

Note:

// to.js
export default function to(promise) {
  return promise.then(data= > {
    return [null, data];
  })
  .catch(err= > [err]);
}

/ * * * * * * /
import to from './to';

async function asyncTask() {

  const [err1, res1] = await to(fn1);
  if(! res1)throw new CustomerError('No res1 found');

  const [err2, res2] = await to(fn2);
  if(err) throw new CustomError('Error occurred while task2');
}
Copy the code

💡 Given an array of urls, how to implement secondary and concurrent interfaces?

Refer to the answer
Async function loadData() {var res1 = await fetch(url1); var res2 = await fetch(url2); var res3 = await fetch(url3); return "when all done"; } // async function loadData(urls) {for (const url of urls) {const response = await fetch(url); console.log(await response.text()); }} /********/ // async function loadData() {var res = await Promise. All ([fetch(url1), fetch(url2), fetch(url3)]); return "when all done"; } // async function loadData(urls) {const textPromises = urls.map(async URL => {const response = await)  fetch(url); return response.text(); }); // Output for (const textPromise of textPromises) {console.log(await textPromise); }}Copy the code

Ah, finally, an async-await is associated with so many knowledge points, I hope it will help you when using it in the future

【 reference 】 :

  1. Developer.mozilla.org/zh-CN/docs/…
  2. Es6.ruanyifeng.com/#docs/itera…
  3. es6.ruanyifeng.com/#docs/async

===🧐🧐