从事件驱动到 async/await
在 async/await 出现之后,异步变得更加简单,这意味着我们不需要重新审视使用 Promise 的简单方法。然而,我认为有一项有用的技术值得分享。
假设我们正在实现事件驱动的代码。为了便于说明,我们通过 Node.js 中的 fs 模块来实现文件读取。虽然现在的 fs 模块已经提供了同步读取文件的方法:
const data = fs.readFileSync(filePath, 'utf8' );
但是我们假装先不知道这一点,来看看我们会如何实现。
假设我们有一个名为 file.txt 的文件,其中包含以下内容:
test
看一下这个基于事件的代码示例:
const fs = require('fs')const filePath = 'xxx/file.txt';fs.readFile(filePath, 'utf8', (err, data) => {if (err) {console.error('文件读取错误:', err)return}console.log(data) // test
})
这种方法存在一些“困难”。例如,我们可能希望将文件内容存储在变量中:
const fs = require('fs')const filePath = 'xxx/file.txt';
let fileContent // 存储文件的变量
fs.readFile(filePath, 'utf8', (err, data) => {if (err) {console.error('文件读取错误:', err)return}fileContent = dataconsole.log(fileContent) test
})
console.log(fileContent) // undefined
打印当然是 undefined,由于 fs.readFile 的异步特性,第二个 console.log 将在第一个之前执行。
我们可能还想将整个文件读取代码包装在一个函数中,并获取内容作为返回值:
const fs = require('fs')
const filePath = 'files/a';function readFile(path) {fs.readFile(path, 'utf8', (err, data) => {if (err) {console.error('文件读取错误:', err)return}return data})
}
const fileContent = readFile(filePath) // 存储文件内容的变量
console.log(fileContent) // undefined.
结果还是 undefined。
实际上,这种方法非常适合防止 JavaScript 阻塞执行流。然而,在不需要阻塞时,就会变得异常头疼。
我们真正想要实现的是:
const fileContent = await readFile(filePath) // 存储文件内容的变量
console.log(fileContent) // test.
为了实现这一点,我们需要采取一些步骤。
首先,我们将基于事件的代码包装在一个承诺中:
const promise = new Promise((resolve, reject) => {fs.readFile(filePath, 'utf8', (err, data) => {if (err) {console.error('文件读取错误:', err)reject()}resolve(data)})
})
promise.then((fileContent) => {console.log(fileContent) // test
})
接下来,我们将 Promise 放入函数中并返回它:
function readFile(path) {return new Promise((resolve, reject) => {fs.readFile(path, 'utf8', (err, data) => {if (err) {console.error('文件读取错误:', err)reject()}resolve(data)})})
}readFile(filePath).then((fileContent) => {console.log(fileContent) // test
})
最后,让我们使用 async/await 语法代替 .then() :
function readFile(path) {return new Promise((resolve, reject) => {fs.readFile(path, 'utf8', (err, data) => {if (err) {console.error('文件读取错误:', err)reject()}resolve(data)})})
}
const fileContent = await readFile(filePath)
console.log(fileContent)
这可能会引发一个错误:
SyntaxError:await 仅在异步函数和模块的顶层主体中有效
除非我们从终端执行 node test.js 独立的 JavaScript 文件,否则我们不应该遇到此错误。但是,如果我们确实遇到此问题,有一个解决方法:
function readFile(path) {return new Promise((resolve, reject) => {fs.readFile(path, 'utf8', (err, data) => {if (err) {console.error('文件读取错误:', err)reject()}resolve(data)})})
}
(async () => { // 将其换成异步const fileContent = await readFile(filePath)console.log(fileContent) // test
})()
就是这样。现在我们有了一个基于 Promise 的解决方案,可以以同步方式调用。
总结
async/await 功能不仅仅是语法糖。正如我们刚刚看到的,它足够强大,可以将“不舒服的”基于事件的代码转换为简单的同步函数。