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:
Generator
It has other uses than just helping you deal with itPromise
- This explanation makes it more difficult for those unfamiliar with the two to understand (because you have to explain the similarities
co
The 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:
Generator
: a similar one{ value: XXX, done: true }
So structuredObject
Async
: always return onePromise
, the use ofawait
or.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