在异步模块中调用没有回调的函数

Calling a function without callback inside a Async module

我对 Async documentation 中的这个例子有疑问:

async.map(['file1','file2','file3'], fs.stat, function(err, results) {
   // results is now an array of stats for each file
});

此示例调用 fs.stat 以在数组的每个元素中使用 (item,callback) 但我不知道回调使用什么,回调定义在哪里?

可以使用节点的内置util.promisify并跳过对async.mapasync.js[=的需要59=]一共-

const { promisify } = require('util')

const fs = require('fs')

const files = ['file1', 'file2', 'file3']

Promise.all(files.map(promisify(fs.stat)))
  .then(results => /* results is now an array of stats for each file */)
  .catch(err => /* err is the first error to occur */)

Promises 是现代 JavaScript 环境中新的并发原语。它们可以很容易地用于 所有 需要节点式错误优先回调的场景,格式为 (err, res) => { ... }async.map.

就是这种情况

Promises 减轻了由臭名昭著的 "callback hell" 引起的一系列问题。如果出于某种原因你不能使用 Promises 而必须使用节点式回调,也许这有助于你的理解。我通过查看完整的工作示例学习得最好,因此我们在不到 50 行的代码中实现了 asyncMap 和一个示例异步函数,让您了解每个部分如何发挥其作用 -

const delayedDouble = (x, callback) =>
  setTimeout        // delay a function
    ( () =>         // the function to delay 
        callback    // call the callback
          ( null    // with no error
          , x * 2   // and the result
          )
    , 1000          // delay the function 1000 milliseconds
    )

const asyncMap = (arr, func, cb) =>
{ const loop = (res, i) =>  // make a named loop
    i >= arr.length         // if i is out of bounds
      ? cb(null, res)       // send no error and the final result
      : func                // otherwise call the user-supplied func
          ( arr[i]          // with the current element 
          , (err, x) =>     // and a callback
              err                     // if there is an error
                ? cb(err, null)       // send error right away, with no result
                : loop                // otherwise keep looping
                    ( [ ...res, x ]   // with the updated result
                    , i + 1           // and the updated index
                    )
          )
  return loop // initialize the loop
     ( []     // with the empty result
     , 0      // and the starting index
     ) 
}
  
asyncMap             // demo of asyncMap
  ( [ 1, 2, 3 ]      // example data
  , delayedDouble    // async function with (err,res) callback
  , (err, res) =>    // final callback for asyncMap
      err            // if an error occured ...
        ? console.error('error', err)  // display error
        : console.log('result', res)   // otherwise display result
  )
  
console.log('please wait 3 seconds...')
// please wait 3 seconds...
// <3 second later>
// result [ 2, 4, 6 ]

以上,delayedDouble 总是 通过调用 callback(null, x * 2) 成功。如果我们有一个有时会失败的函数,我们可以看到 asyncMap 正确地传递了错误

const tenDividedBy = (x, callback) =>
  setTimeout
    ( () =>
        x === 0
          // when x is zero, send an error and no result
          ? callback(Error('cannot divide 10 by zero'), null)
          // otherwise, send no error and the result
          : callback(null, 10 / x)
    , 1000
    )

asyncMap
  ( [ 1, 0, 6 ]   // data contains a zero!
  , tenDividedBy
  , (err, res) =>
      err
        ? console.error('error', err)
        : console.log('result', res)
  )
  // error Error: cannot divide 10 by zero

如果没有报错,结果如期而至-

asyncMap
  ( [ 1, 2, 3, ]
  , tenDividedBy
  , (err, res) =>
      err
        ? console.error('error', err)
        : console.log('result', res)
  )
  // result [ 10, 5, 3.3333333333333335 ]

我们可以通过查看使用 Promises 而不是回调编写的相同程序来证明我们使用 Promises 的理由。正如您在下面看到的,Promises 允许代码保持更扁平。还要注意 asyncMap 如何不需要关心代码的错误分支;错误会自动冒出并可以在异步计算的任何时候使用 .catch 捕获 -

const asyncMap = (arr, func) =>
{ const loop = (res, i) =>
    i >= arr.length
      ? Promise.resolve(res)
      : func(arr[i]).then(x => loop([...res, x], i + 1))
  return loop ([], 0)
}

const tenDividedBy = x =>
  x === 0
    ? Promise.reject(Error('cannot divide 10 by zero'))
    : Promise.resolve(10 / x)

asyncMap([1, 2, 0], tenDividedBy)
  .then(res => console.log('result', res))
  .catch(err => console.error('error', err))
// Error: cannot divide 10 by zero

asyncMap([1, 2, 3], tenDividedBy)
  .then(res => console.log('result', res))
  .catch(err => console.error('error', err))
// result [ 10, 5, 3.3333 ]

这是一个很好的练习,但是这个答案的第一部分建议使用 Promise.allPromise.all 的存在使我们不必手写 asyncMap 之类的东西。作为一个额外的好处,Promise.all 并行处理计算,而不是串行处理。