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

时间:2019-02-12 18:09:54

标签: javascript fs async.js

我对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)在数组的每个元素中使用,但我不知道回调的用途,回调的定义在哪里?

1 个答案:

答案 0 :(得分:-1)

可以使用节点的内置util.promisify并完全不需要使用async.map async.js -

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 */)

Promise是现代JavaScript环境中的新并发原语。它们可以轻松用于 all 场景,该场景中的节点样式为错误优先回调,格式为(err, res) => { ... }async.map就是这种情况。

承诺减轻了因臭名昭著的“回调地狱”而引起的一系列问题。如果出于某种原因您不能使用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的理由。如下所示,Promise允许代码保持扁平化。另请注意,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并行而不是串行地处理计算。