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

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

doSomething(param, (err, result) => {

})

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

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

const myFunction = () => {
  doSomething(param, (err, result) => {
    return result //無法從 `myFunction` 中返回這個結果
  })
}
const myFunction = callback => {
  doSomething(param, (err, result) => {
    callback(result) //不行
  })
}

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

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

const { promisify } = require('util')

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

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

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

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

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)
      })
    })
  })
})

改成更簡潔的寫法:

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 來使用它:

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)