浅谈async、await 实现原理

117 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第30天,点击查看活动详情

🎈大家好,我是李乐一,新人初来乍到,请多多关照~

📝小小的前端一枚,分享一些日常的学习和项目实战总结~

😜如果本文对你有帮助的话,帮忙点点赞呀!ღ( ´・ᴗ・` )比心~

写在前面

我们常用的实现异步的方法有很多,比如Promise等,本文主要记录一下async,await的实现。温故而知新。

JavaScript 异步编程

JavaScript 是单线程,支持异步编程才能提高运行效率。异步编程的语法是让异步过程写起来更像同步过程。

1. 回调函数

回调函数,就是把任务的第二段放在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。

📝举个栗子

const fs = require('fs')
fs.readFile('', (err, data) => {
    if (err) {
        console.error(err)
        return
    }
    console.log(data.toString())
})

回调函数最大的问题是容易形成回调地狱,也就是多个回调函数嵌套,降低代码可读性,增加逻辑的复杂性

📝举个栗子

fs.readFile(fileA, function (err, data) {
    fs.readFile(fileB, function (err, data) {
        // ...
    })
})

2. Promise

为解决回调函数的不足,出现了新的语法 Promise

📝举个栗子

const fs = require('fs')

const readFileWithPromise = file => {
    return new Promise((resolve, reject) => {
        fs.readFile(file, (err, data) => {
            if (err) {
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}

readFileWithPromise('')
    .then(data => {
        console.log(data.toString())
        return readFileWithPromise('/etc/profile')
    })
    .then(data => {
        console.log(data.toString())
    })
    .catch(err => {
        console.log(err)
    })

Promise 的工作原理是将回调函数的横向加载变成纵向加载,达到链式调用的效果,避免了回调地狱的出现。其最大问题是代码冗余,增加了大量的 then操作,原来的语义变得很不清楚。

3. async、await

为了更好的实现异步功能,出现了async、await

📝举个栗子

const fs = require('fs')
async function readFile() {
    try {
        var f1 = await readFileWithPromise('')
        console.log(f1.toString())
        var f2 = await readFileWithPromise('')
        console.log(f2.toString())
    } catch (err) {
        console.log(err)
    }
}

async、await 函数写起来跟同步函数一样,接收 Promise 或原始类型的值。

async、await

1. generator

generator 函数是协程在 ES6 的实现。协程简单来说就是多个线程互相协作,完成异步任务。 整个 generator 函数就是一个封装的异步任务,异步操作需要暂停的地方,都用 yield 语句注明。generator 函数的执行方法如下:

📝举个栗子

function* gen(x) {
    console.log('start')
    const y = yield x * 2
    return y
}

const g = gen(1)
g.next()   // start { value: 2, done: false }
g.next(4)  // { value: 4, done: true }
  • gen() 不会立即执行,而是一上来就暂停,返回一个 Iterator 对象
  • 每次 g.next() 都会打破暂停状态去执行,直到遇到下一个 yield 或者 return
  • 遇到 yield 时,会执行 yield 后面的表达式,并返回执行之后的值,然后再次进入暂停状态,此时 done: false 。
  • next 函数可以接受参数,作为上个阶段异步任务的返回结果,被函数体内的变量接收
  • 遇到 return 时,会返回值,执行结束,即 done: true
  • 每次 g.next() 的返回值永远都是 {value: ... , done: ...} 的形式

2. async、await

async、await 可以看作自带启动器的 generator 函数的语法糖。不同的是,async、await 只支持 Promise 和原始类型的值,不支持 thunk 函数。

📝举个栗子

async function readfile() {
    try {
        const content1 = await readFileWithPromise('', 'utf8')
        console.log(content1)
        const content2 = await readFileWithPromise('e', 'utf8')
        console.log(content2)
        return 'done'
    } catch (err) {
        throw(err)
    }
}
readfile().then(
    res => console.log(res),
    err => console.error(err)
)