review

Asynchronous is when one part of the program is running now and another part is running in the future. The whole point of asynchronous processing is how to handle the part that will run in the future.

Callbacks are the most basic asynchronous pattern in JavaScript, which is to make an appointment to do something in the future and call it later. Simple and straightforward, but there are also problems such as mistrust and deep nested calls. For those of us who write and maintain code, the human brain is used to linear processing.

The problem with callback-based asynchrony prompted us to seek a mechanism to ensure that callbacks can be trusted and that asynchrony can be better expressed. That’s when promises come in, and promises don’t come in to replace callbacks. Instead, callback is handed over to a trusted intermediary between us and the other tools. Promise chains also provide (though not perfect) a better way to express asynchronous flows in a sequential manner, which helps our brains better plan and maintain asynchronous JavaScript code.

The generator

Promise managed callbacks in an orderly and reliable way, but we wanted to express asynchrony as synchronous.

We already know that a generator is a factory function that acts as a production iterator, and we also know that a generator is a messaging system.

Why generators

Before generators, program code, once executed, never stopped until the end of the program 🔚. However, in the generator code can be paused, and can communicate with outside the generator ☎️, and then resume execution. If you think back to asynchronous process control, we were trying to make asynchronous tasks express synchronously. Now we can implement this idea with the help of generators 💡.

Given the nature of the generator, we should know that when the generator is executing an asynchronous task, it is perfectly possible to place the asynchronous task outside the generator and return to the 🔙 generator to resume execution after the asynchronous task has finished. Keep in mind that the generator suspends only internal state; the rest of the program runs normally. This way, all the code inside the generator appears to be expressed synchronously.

It is also important to note that generators are just a new expression for 🆕 and have nothing to do with asynchronous or synchronous 💰. Since it doesn’t matter, it doesn’t matter when it comes to asynchronous mode selection. Given that the asynchronous series is incremental, we use the Promise + generator pattern to express asynchracy.

A combination of generators and promises

In terms of asynchronous process control, a generator is made up of two parts. One part is that the generator’s internal code expresses tasks synchronously, and the other part is that iterators generated by the generator handle asynchrony.

const async = n= > {
    return new Promise(resolve= > {
        setTimeout((a)= > {
            resolve(The first `${n}Asynchronous task ')},0); })};const generator = function *generator(){
    const response_1 = yield async(1);
    const response_2 = yield async(2);
    const response_3 = yield async(3);
    console.log('response_1: %s; response_2: %s; response_3: %s; ',response_1,response_2,response_3);
};

const gen = generator();
const gen_1 = generator();
console.log('gen_next_1: %s; gen_next_2: %s; gen_next_3: %s; ', gen_1.next().value, gen_1.next().value, gen_1.next().value);
gen.next().value.then(yield_1= > {
    console.log('yield_1: %s; ', yield_1);
    return gen.next(yield_1).value.then(yield_2= > {
        console.log('yield_2: %s; ', yield_2)
        return gen.next(yield_2).value.then(yield_3= > {
            console.log('yield_3: %s', yield_3);
            returngen.next(yield_3); })})});// gen_next_1: [object Promise]; gen_next_2: [object Promise]; gen_next_3: [object Promise];
// yield_1: the first asynchronous task;
// yield_2: the second asynchronous task;
// yield_3: Third asynchronous task
// response_1: first asynchronous task; Response_2: second asynchronous task; Response_3: the third asynchronous task;
Copy the code

If you just look at generator functions, the inside of the function is written like synchronization. Gen and GEN_1 are both instances of the same generator.

As mentioned earlier, there are two ways to understand this piece of code: ———— iteration and messaging. Iteration properties are not covered here, but are now focused on messaging properties. In generators, the generator function is not executed immediately after being called, but instead constructs an iterator. It is yield/ Next that generators rely on for two-way communication between the inside and outside of the generator.

Inside a generator, yield is a keyword/expression used to pause (completely preserve its state) and pass data externally (the function also starts out in an unexecuted state). Outside of the generator, Next has the ability to restore the generator and pass data inside the generator.

Chaos initial (gen created), Pangu created the world (the first next() implementation), the beginning of heaven and earth, after Nuwa made man, everything is thriving. Gonggong and Zhurong two naughty hit zhoushan, to Nuwa out of a problem (yield), Chinese history stood still. Nuwa up days for help (yield async (1)), and god answered and brought the multicolored (yield_1), nuwa smooth day, Chinese history once again set off (next (yield_1)).

However, the good times did not last long. The Huaxia tribe was often harassed and invaded by Chiyou tribe. The existence of Chiyou again hindered the advance of Chinese history (yield). Yield_2 Yellow Emperor yield_2 Yellow Emperor Yield_2 Yellow Emperor Yield_2 Yellow Emperor Yield_2 Yellow Emperor Yield_2 Yellow Emperor Yield_2

However, the good times did not last long. The flood of the Central Plains stopped the Chinese history again. Yield_3 Xia Yu yield_3 Xia Yu yield_3 Xia Yu yield_3 Xia Yu yield_3 Xia Yu yield_3 Xia Yu yield_3

I can’t do this anymore. I’m glad it’s over. 😓 code runs like this. The generator generates data internally and then throws it to the iterator for consumption, which in turn throws the result of execution back to the generator. It’s that simple. Just don’t get too complicated.

By two-way messaging, I mean more than just the data inside and outside the generator under normal circumstances. For exception errors, both inside and outside the generator can be caught bidirectionally. Because the pause inside the generator preserves its context, try… Catch is back in the game.

Generator self-executes & async/await

The Promise + generator expression for asynchrony is implemented, however we should note that the part of controlling the generator with iterators is too tedious. It would be nice if it could be encapsulated as follows:

const generator_wrap = function (generator) {
    const args = [...arguments].slice(1);
    const gen = generator.apply(this, args);
    return new Promise((resolve, reject) = > {
        const handleNext = function handleNext(yield){
            let next;
            try {
                next = gen.next(yield);
            } catch (error) {
                reject(error)
            }
            if (next.done) {
                resolve(next.value)
            } else {
                return Promise.resolve(next.value).then(yield= > {
                    handleNext(yield); }, error => { gen.throw(error); })}}; handleNext(); })};// ———————————— manual splitter ————————————
const generator = function *generator(){
    const response_1 = yield async(1);
    const response_2 = yield async(2);
    const response_3 = yield async(3);
    console.log('response_1: %s; response_2: %s; response_3: %s; ',response_1,response_2,response_3);
};

generator_wrap(generator);
// response_1: first asynchronous task; Response_2: second asynchronous task; Response_3: the third asynchronous task;
Copy the code

Instead of looking at the generator_wrap function, just look below the dividing line. At this point, the expression of an asynchronous process is getting closer to the ideal. The generator_wrap function still needs to be wrapped manually, but now it’s not 😄

ES2017 has introduced async/await, we no longer have to manage generators ourselves, simple, powerful and convenient async/await handles everything for us.

const awati_async = async() = > {const response_1 = await async(1);
    const response_2 = await async(2);
    const response_3 = await async(3);
    console.log('response_1: %s; response_2: %s; response_3: %s; ', response_1, response_2, response_3);
};

awati_async();
// response_1: first asynchronous task; Response_2: second asynchronous task; Response_3: the third asynchronous task;
Copy the code

At this point, the asynchronous expression of JavaScript is over for the time being 👋.

Asynchronous JavaScript series:

Asynchronous JavaScript(callback)

Asynchronous JavaScript(Promise article)

Asynchronous JavaScript (Final) attachment (from iterator pattern to iteration protocol)

References:

Iterators and generators

JavaScript you Didn’t Know (Middle Volume)

JavaScript you Don’t Know (Vol. 2)

Asynchronous application of Generator functions