preface

Before reading this article, you need to have some understanding of async-await and generator. If you don’t know, check out the previous article on the use of async-await and generator-iterators

Put forward the demand

There is currently a network request function, and I want to make sure that the parameters passed in the next network request are based on the results of the previous network request. For example, pass ‘aaa’ the first time, then add ‘BBB’ to the result of the first request the second time, and so on.

function request(url) {
    return new Promise((resolve, reject) = > {
        setTimeout(() = >{  // Use timer to simulate network request
            resolve(url)
        }, 1000)})}Copy the code

The solution

Promise to realize

request('aaa').then(res= > {
    // where res is 'aaa'
    request(res + 'bbb').then(res= > {
        // where res is 'aaabbb'
        request(res + 'ccc').then(res= > {
            console.log(res)  // 'aaabbbccc'})})})Copy the code
  • As you can see, the Promise. Then method above can be used to practice the desired effect, and continue the request in then to achieve the desired effect. However, the code above also has multiple layers of THEN nesting, and currently only sends three network requests. If it sends 30, this code will create callback hell.

Async and await

async function foo() {
    const result1 = await request('aaa')  // result1 results in 'aaa'
    const result2 = await request(result1 + 'bbb') // result2 results in 'aaabbb'
    const result3 = await request(result2 + 'ccc')
    console.log(result3)  // 'aaabbbccc'
}

foo()
Copy the code
  • We can see that using async and await can also achieve the desired effect, and using this method allows us to write code using synchronous methods. This is very friendly for us to read the code.
  • This is also the recommended solution to this requirement.

Implementation using generator

// Define the generator function
function* generator() {
    const result1 = yield request('aaa') // The first call to next stops here
    const result2 = yield request(result1 + 'bbb') // The second call to next will stop here
    const result3 = yield request(result2 + 'ccc') // A third call to next will stop here
    // Call next for the fourth time to execute all the following code
    console.log(result3) / / print 'aaabbbccc'
}

const gene = generator() // Call the generator function to generate the generator
// Calling next returns an iterator whose value is the Promise returned by Request ('aaa')
gene.next().value.then(res1= > {
    // res1 results in 'aaa'
    // Call next and pass in res1, which is passed to result1 in the generator function
    // The next function returns an iterator whose value is the Promise returned by Request (result1 + 'BBB ')
    gene.next(res1).value.then(res2= > {
        // res2 results in 'aaabbb'
        // Call the next function and pass in res2, which is passed to the result2 in the generator function
        // The next function returns an iterator whose value is the Promise returned by Request (result2 + 'CCC ')
        gene.next(res2).value.then(res3= > {
            // res3 results in 'aaabbbCCC'
            // Call next and pass res3, which is passed to result3 in the generator function
            gene.next(res3)
        })
    })
})
Copy the code
  • The code above might feel a little hard to see, and it looks worse than the callback implemented with Promise.
  • However, if we look at generator functions alone, we will see that writing in generator functions is basically the same as writing with async-await except for the different keywords.
  • Looking at the next function below, we can see that there is a pattern, so we can use a recursive function to help us implement this process without having to write it manually.

Implements a function that automatically performs the next operation

function execuGene(generatorFn) {
    // Execute the passed generator function to get the generator
    const gene = generatorFn()
    // Define a recursive function
    function recurse(url) {
        // Call next and pass the URL argument to get the iterator
        const result = gene.next(url)
        // Determine the done state of the iterator
        if(! result.done) {// Enter here, indicating that the generator is not finished
            result.value.then(res= >{
                // Call recurse recursively
                recurse(res)
            })
        }
    }
    
    recurse()
}

function* generator() {
    const result1 = yield request('aaa') // The first call to next stops here
    const result2 = yield request(result1 + 'bbb') // The second call to next will stop here
    const result3 = yield request(result2 + 'ccc') // A third call to next will stop here
    // Call next for the fourth time to execute all the following code
    console.log(result3) / / print 'aaabbbccc'
}
// Now you just need to change the next code to this one line to achieve the same effect
execuGene(generator)
Copy the code
  • You can see that after one handler, the code we are writing is basically the same as the async-await implementation. This is how async-await is implemented.

conclusion

Async-await is essentially syntactic sugar for generator and Promise. The precise control of the function is realized through the yield keyword of generator function, and asynchronous operation is realized through synchronous code. We can also use a recursive function to help with the tedious operation of the next call to the generator.