/

如何在 Node.js 的回調函數中使用 promises 和 await

如何在 Node.js 的回調函數中使用 promises 和 await

大部分的 Node.js API 在還沒有 promises 的時候就已經建立了,在這些 API 中,使用了回調函數的解決方案。

一般的 Node.js API 使用方式如下:

1
2
3
doSomething(param, (err, result) => {

})

這個也同樣適用於一些庫,例如 node-redis。當我在一個項目中使用它時,到了某個時候我真的需要把所有的回調函數都移除掉,因為我有太多層次的嵌套回調函數,這種情況下就會陷入所謂的 “回調地獄”。

另外,有時候必須避免使用回調函數,因為你需要從函數中返回函數調用的結果。如果這個結果是通過回調函數返回的,那麼想要獲取結果的唯一方法就是將它作為另一個函數的參數返回,進而持續進行回調函數的操作。

1
2
3
4
5
const myFunction = () => {
doSomething(param, (err, result) => {
return result //無法從 `myFunction` 中返回這個結果
})
}
1
2
3
4
5
6
7
8
9
const myFunction = callback => {
doSomething(param, (err, result) => {
callback(result) //不行
})
}

myFunction(result => {
console.log(result)
})

但是,有一個簡單的解決方案,這也是由 Node.js 自身提供的。我們可以通過使用內置的 util 模塊中的 promisify 方法 “promisify” 一個不支援 promises 的函數(以及相應的 async/await 語法)。

1
const { promisify } = require('util')

然後使用它創建新的函數:

1
2
3
const ahget = promisify(client.hget).bind(client)
const asmembers = promisify(client.smembers).bind(client)
const ahkeys = promisify(client.hkeys).bind(client)

可以看到,我添加了 a 字母來表示「異步」。

現在,我們可以將這個 “回調地獄” 的例子改為更簡潔的版本:

1
2
3
4
5
6
7
8
9
client.hget(`user:${req.session.userid}`, 'username', (err, currentUserName) => {
client.smembers(`followers:${currentUserName}`, (err, followers) => {
client.hkeys('users', (err, users) => {
res.render('dashboard', {
users: users.filter((user) => user !== currentUserName && followers.indexOf(user) === -1)
})
})
})
})

改成更簡潔的寫法:

1
2
3
4
5
6
7
const currentUserName = await ahget(`user:${req.session.userid}`, 'username')
const followers = await asmembers(`followers:${currentUserName}`)
const users = await ahkeys('users')

res.render('dashboard', {
users: users.filter((user) => user !== currentUserName && followers.indexOf(user) === -1)
})

這在使用一個沒有直接訪問權限的函數時非常有效,比如此例中使用了一個第三方庫。

在底層,promisify 將這個函數包裹在一個 promise 中,並返回它。

你也可以手動這樣做,即從一個函數返回一個 promise,然後再使用 async/await 來使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const handleLogin = (req, user) => {
return new Promise((resolve, reject) => {
req.login(user, (err) => {
if (err) {
return reject({
error: true,
message: err,
})
}
return resolve({
success: true,
})
})
})
}

//...
const resultLogin = await handleLogin(req, user)

tags: [“Node.js”, “promises”, “async/await”, “callback”, “util”]