Since Promise, JavaScript has been introducing new features that help make it easier to handle asynchronous programming and help us avoid callback hell. Promise is the basis for Generator/yield and async/await, which I hope you’ve seen in advance.

Around the time of ES6, the keywords Generator/yield were introduced, and using Generator was a handy way to set up an interpreter to handle promises.

Then, around ES7, we got async/await syntax, which allows us to write asynchronous code in a similar way to writing synchronous code (without using.then() or callback functions).

Both are very convenient for asynchronous programming, but again, there are a few differences between the two.

Generator

Generator is a function that returns a value inside the function by yield (at this point, the execution of the Generator function is tentative until the next firing. Next ()). You create a Generator function by adding an * after the function keyword.

After calling a Generator function, the code is not immediately executed. Instead, the function returns a Generator object, whose next function yields /return values. Whether yield or return is triggered, the next() function always returns an object with value and done attributes. Value is the return value, and done is a Boolean object that identifies whether the Generator can continue to provide the return value. P.S. Generator functions are executed lazily; yield code is executed only when next is triggered

function * oddGenerator () {
  yield 1
  yield 3

  return 5
}

let iterator = oddGenerator()

let first = iterator.next()  // { value: 1, done: false }
let second = iterator.next() // { value: 3, done: false }
let third = iterator.next()  // { value: 5, done: true }
Copy the code

Parameter passing for next

We can pass an argument when we call next(), which can be received before the last yield:

function * outputGenerator () {
  let ret1 = yield 1
  console.log(`got ret1: ${ret1}`)
  let ret2 = yield 2
  console.log(`got ret2: ${ret2}`)}let iterator = outputGenerator()

iterator.next(1)
iterator.next(2) // got ret1: 2
iterator.next(3) // got ret2: 3
Copy the code

It may seem odd at first glance that the first log is output only on the second call to next, which goes back to the implementation of the Generator above, which states that yield and return are syntax for returning values. When a function encounters these two keywords during execution, execution is paused for the next activation. Then let ret1 = yield 1, which is an assignment expression, that is, the part to the right of = is executed first, the yield keyword is encountered during the execution of =, and the function is paused at this point, and will be activated the next time next() is fired, at which point, We continue with the assignment statement let ret1 = XXX that was not completed last time and pause when yield is encountered again. This explains why the argument to the second call to next() is received by the variable assigned by the first yield

Used as an iterator

Since the Generator object is an iterator, we can use it directly for the for of loop:

Note, however, that as used in iterators, only yield return values are not counted in the iteration

function * oddGenerator () {
  yield 1
  yield 3
  yield 5

  return 'won\'t be iterate'
}

for (let value of oddGenerator()) {
  console.log(value)
}
/ / > 1
/ / > 3
/ / > 5
Copy the code

Generator Generator within a function

In addition to the yield syntax, there is a yield* syntax that can be roughly understood as a Generator version of the […] syntax. Used to expand Generator iterators.

function * gen1 () {
  yield 1
  yield* gen2()
  yield 5
}

function * gen2 () {
  yield 2
  yield 3
  yield 4
  return 'won\'t be iterate'
}

for (let value of gen1()) {
  console.log(value)
}
/ / > 1
/ / > 2
/ / > 3
/ / > 4
/ / > 5
Copy the code

The simulation implements the Promise executor

Then we implemented a simple executor in combination with Promise.

The most popular such library is: CO

function run (gen) {
  gen = gen()
  return next(gen.next())

  function next ({done, value}) {
    return new Promise(resolve= > {
     if (done) { // finish
       resolve(value)
     } else { // not yet
       value.then(data= > {
         next(gen.next(data)).then(resolve)
       })
     }
   })
  }
}

function getRandom () {
  return new Promise(resolve= > {
    setTimeout(_= > resolve(Math.random() * 10 | 0), 1000)})}function * main () {
  let num1 = yield getRandom()
  let num2 = yield getRandom()

  return num1 + num2
}

run(main).then(data= > {
  console.log(`got data: ${data}`);
})
Copy the code

A simple interpreter simulation (for example only)

In our example, we agreed that yield must be a Promise function and we’ll just look at the code for main(). Using Generator does allow us to write asynchronous code in an almost synchronous manner. However, This means that we must have an external function that performs the main() function as a Generator for us, handles the Promise generated within it, and returns the result to the Generator in the THEN callback so that we can execute the code below.

Async

We rewrite the Generator example above with async/await:

function getRandom () {
  return new Promise(resolve= > {
    setTimeout(_= > resolve(Math.random() * 10 | 0), 1000)})}async function main () {
  let num1 = await getRandom()
  let num2 = await getRandom()

  return num1 + num2
}

console.log(`got data: The ${await main()}`)
Copy the code

It looks as if we could switch from Generator/yield to async/await by changing both * to async and yield to await. Many people use Generator/yield to explain async/await behavior, but this leads to the following problems:

  1. GeneratorIt has other uses than just helping you deal with itPromise
  2. This explanation makes it more difficult for those unfamiliar with the two to understand (because you have to explain the similaritiescoThe library)

Async /await is an extremely convenient way to handle promises, but it can also cause some headaches if used incorrectly

The Async function always returns a Promise

An async function, whether you return 1 or throw new Error(). On the caller’s side, we always receive a Promise object:

async function throwError () {
  throw new Error()}async function returnNumber () {
  return 1
}

console.log(returnNumber() instanceof Promise) // true
console.log(throwError() instanceof Promise)   // true
Copy the code

That is, no matter what the function does, you have to deal with it as promised.

Await is executed sequentially and cannot be executed in parallel

JavaScript is single threaded, which means that await can only be processed one at a time. If you have multiple promises to be processed, it means that you have to wait for the previous Promise to be processed before the next one can be processed, which means that if we send a lot of requests at once, processing will be very slow. One by one:

const bannerImages = []

async function getImageInfo () {
  return bannerImages.map(async banner => await getImageInfo(banner))
}
Copy the code

Like this four timer, we need to wait for 4s to complete execution:

function delay () {
  return new Promise(resolve= > setTimeout(resolve, 1000))}let tasks = [1.2.3.4]

async function runner (tasks) {
  for (let task of tasks) {
    await delay()
  }
}

console.time('runner')
await runner(tasks)
console.timeEnd('runner')
Copy the code

In this case, we can optimize as follows:

function delay () {
  return new Promise(resolve= > setTimeout(resolve, 1000))}let tasks = [1.2.3.4]

async function runner (tasks) {
  tasks = tasks.map(delay)
  await Promise.all(tasks)
}

console.time('runner')
await runner(tasks)
console.timeEnd('runner')
Copy the code

“Await *” is mentioned in the draft, but it seems that it is not a standard yet, so we still use the method of wrapping one layer with promise. all to implement it

We know that the Promise object executes the code inside the function when it’s created, which means that when we create this array with the map, all of the Promise code executes, which means that all of the requests are issued at the same time, We then listen for all Promise responses with await promise.all.

conclusion

Both Generator and Async function return an object of a specific type:

  1. Generator: a similar one{ value: XXX, done: true }So structuredObject
  2. Async: always return onePromise, the use ofawaitor.then()To get the return value

Generator Generator is a class of generators, a special type of iterator that feels a bit out of place for solving asynchronous callbacks. Async, on the other hand, is a syntax for making use of promises more succinctly, and is more focused than implementations such as Generator + CO, designed to handle asynchronous programming.

It’s 2018 and async has been around for a long time, so let the Generator do what it needs to do.

The resources

  • modern-javascript-and-asynchronous-programming-generators-yield-vs-async-await
  • async-function-tips

Example code: code-resource