在 Node.js 中使用 async 函数的方法

本文介绍了Node.js如何从7.6版开始支持async函数,随着Node.js8成为长期支持版本,async函数已成为编写异步代码的标准方式。async函数让基于Promise的代码看起来更像同步代码,使用await关键字可以等待Promise的解决。文章提供了从回调函数向async函数迁移的策略,包括使用util.promisify方法。此外,还讨论了在Express中使用async函数的最佳实践,强调了错误处理的重要性,并展示了如何并行执行异步操作。

前言

借助于新版 V8 引擎,Node.js 从 7.6 开始支持 async 函数特性。今年 10 月 31 日,Node.js 8 也开始成为新的长期支持版本,因此你完全可以放心大胆地在你的代码中使用 async 函数了。在这边文章里,我会简要地介绍一下什么是 async 函数,以及它会如何改变我们编写 Node.js 应用的方式。

什么是 async 函数

利用 async 函数,你可以把基于 Promise 的异步代码写得就像同步代码一样。一旦你使用 async 关键字来定义了一个函数,那你就可以在这个函数内使用 await 关键字。当一个 async 函数被调用时,它会返回一个 Promise。当这个 async 函数返回一个值时,那个 Promise 就会被实现;而如果函数中抛出一个错误,那么 Promise 就会被拒绝。

await 关键字可以被用来等待一个 Promise 被解决并返回其实现的值。如果传给 await 的值不是一个 Promise,那它会把这个值转化为一个已解决的 Promise。

const rp = require('request-promise')
async function main () {
 const result = await rp('https://google.com')
 const twenty = await 20
 // 睡个1秒钟
 await new Promise (resolve => {setTimeout(resolve, 1000)
 })
 return result
}
main()
 .then(console.log)
 .catch(console.error) 

向 async 函数迁移

如果你的 Node.js 应用已经在使用Promise,那你只需要把原先的链式调用改写为对你的这些 Promise 进行 await。

如果你的应用还在使用回调函数,那你应该以渐进的方式转向使用 async 函数。你可以在开发一些新功能的时候使用这项新技术。当你必须调用一些旧有的代码时,你可以简单地把它们包裹成为 Promise 再用新的方式调用。

要做到这一点,你可以使用内建的 util.promisify方法:

const util = require('util')
const {readFile} = require('fs')
const readFileAsync = util.promisify(readFile)
async function main () {
 const result = await readFileAsync('.gitignore')
 return result
}
main()
 .then(console.log)
 .catch(console.error) 

3 Async 函数的最佳实践

在 express 中使用 async 函数

express 本来就支持 Promise,所以在 express 中使用 async 函数是比较简单的:

const express = require('express')
const app = express()
app.get('/', async (request, response) => {
 // 在这里等待 Promise
 // 如果你只是在等待一个单独的 Promise,你其实可以直接将将它作为返回值返回,不需要使用 await 去等待。
 const result = await getContent()
 response.send(result)
})
app.listen(process.env.PORT) 

但正如 Keith Smith 所指出的,上面这个例子有一个严重的问题——如果 Promise 最终被拒绝,由于这里没有进行错误处理,那这个 express 路由处理器就会被挂起。

为了修正这个问题,你应该把你的异步处理器包裹在一个对错误进行处理的函数中:

const awaitHandlerFactory = (middleware) => {
 return async (req, res, next) => {try { await middleware(req, res, next)} catch (err) { next(err)}
 }
}
// 然后这样使用:
app.get('/', awaitHandlerFactory(async (request, response) => {
 const result = await getContent()
 response.send(result)
})) 

并行执行

比如说你正在编写这样一个程序,一个操作需要两个输入,其中一个来自于数据库,另一个则来自于一个外部服务:

async function main () {
 const user = await Users.fetch(userId)
 const product = await Products.fetch(productId)
 await makePurchase(user, product)
} 

在这个例子中,会发生什么呢?

你的代码会首先去获取 user,

然后获取 product,

最后再进行支付。

如你所见,由于前两步之间并没有相互依赖关系,其实你完全可以将它们并行执行。这里,你应该使用 Promise.all 方法:

async function main () {
 const [user, product] = await Promise.all([Users.fetch(userId),Products.fetch(productId)
 ])
 await makePurchase(user, product)
} 

而有时候,你只需要其中最快被解决的 Promise 的返回值——这时,你可以使用 Promise.race 方法。

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

### 核心结论 **Node.js 中非 `async` 函数默认不一定是同步的**,其执行模式取决于函数内部是否包含异步操作。关键区分点在于: - **同步函数**:直接返回结果,无 I/O 或定时器操作 - **异步函数**:包含回调/Promise/定时器等异步逻辑 --- ### 1. 核心判断标准 | 特征 | 同步函数 | 异步函数 | |---------------------|--------------------------------|----------------------------------| | **返回值类型** | 直接返回结果 | 返回 `Promise` 或使用回调 | | **典型方法** | `fs.readFileSync` | `fs.readFile` | | **阻塞性** | 阻塞事件循环 | 非阻塞 | | **错误处理** | `try/catch` | `.catch()` 或回调参数处理 | ```javascript // 同步函数示例 function syncAdd(a, b) { return a + b; // 直接返回 } // 异步函数示例(无async关键字!) function asyncReadFile(callback) { fs.readFile('file.txt', callback); // 回调模式 } ``` --- ### 2. Node.js 内置模块的混合性 Node.js 大部分核心模块同时提供同步/异步版本: | 模块 | 同步方法 | 异步方法 | |-----------|----------------------|-----------------------| | `fs` | `readFileSync` | `readFile` | | `crypto` | `pbkdf2Sync` | `pbkdf2` | | `child_process` | `execSync` | `exec` | ```javascript // 典型使用对比 const syncData = fs.readFileSync('file.txt'); // 同步阻塞 fs.readFile('file.txt', (err, data) => { /* 异步非阻塞 */ }); ``` --- ### 3. 异步函数的识别方法 **即使没有 `async` 关键字,以下情况也是异步函数**: 1. **回调模式**: ```javascript function setTimeout(callback, delay) { /*...*/ } // 经典异步 ``` 2. **返回 Promise**: ```javascript function fetchData() { return new Promise(resolve => { /*...*/ }); // 异步 } ``` 3. **使用事件发射器**: ```javascript const net = require('net'); const server = net.createServer(); // 事件驱动异步 ``` --- ### 4. 特殊边界案例 **看似同步实为异步的操作**: ```javascript // 案例1:微任务(Promise.resolve) function sneakyAsync() { let resolved = false; Promise.resolve().then(() => { resolved = true }); return resolved; // 返回false,但后续有微任务 } // 案例2:process.nextTick function nextTickAsync() { process.nextTick(() => { console.log('异步执行') }); console.log('同步执行'); } // 输出顺序:同步执行 → 异步执行 ``` --- ### 5. 执行流程验证实验 **实验:混合同步/异步函数调用栈** ```javascript function syncFunc() { console.log('sync'); } function asyncFunc() { setImmediate(() => console.log('async')); } syncFunc(); // 1. 同步立即执行 asyncFunc(); // 2. 同步调用异步函数 console.log('main'); // 3. 同步执行 // 输出顺序: // sync → main → async ``` --- ### 开发者必备鉴别技巧 1. **查看官方文档**:Node.js 文档会明确标注同步/异步 2. **观察返回值**: - 返回 `Promise` → 异步 - 接受回调参数 → 异步 3. **性能测试**: ```javascript console.time('test'); suspectFunction(); // 被测函数 console.timeEnd('test'); // 若显示0ms可能是异步(需结合其他判断) ``` --- ### 最佳实践建议 1. **统一代码风格**: ```javascript // 明确使用async/await或回调,避免混用 async function modernAsync() { /*...*/ } function legacyAsync(callback) { /*...*/ } ``` 2. **错误处理区分**: ```javascript // 同步错误 try { syncOp(); } catch (err) {} // 异步错误 asyncOp().catch(err => {}); ``` 3. **避免同步阻塞**: ```javascript // 在服务器代码中禁用同步IO // ❌ const data = fs.readFileSync('largefile.json'); // ✅ await fs.promises.readFile('largefile.json'); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值