【问题标题】:Calling a function without callback inside a Async module在异步模块内调用没有回调的函数
【发布时间】:2019-07-06 11:03:22
【问题描述】:

我对@9​​87654321@ 中的这个例子有疑问:

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

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

【问题讨论】:

  • 回调函数由异步库提供。你也可以写function(item, callback) { fs.stat(item, callback); }
  • 您提供的链接还记录了iteratee(这是一个AsyncFunction)和callback 参数(定义为(err, results))。
  • @Bergi 我假设发生了这样的事情,但我在文档中没有找到任何内容
  • @FernandoLlallire 您链接的文档说“iteratee 应用于coll 中的每个项目,并使用(item, callback) 调用。callback 参数是由图书馆创建,在其内部某处定义。有关系吗?

标签: javascript fs async.js


【解决方案1】:

可以使用 node 的内置 util.promisify 并完全不需要 async.mapasync.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 */)

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

Promise 缓解了臭名昭著的“回调地狱”引起的一系列问题。如果由于某种原因您不能使用 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 以并行方式而不是串行方式处理计算。

【讨论】:

  • 那个可以倾向于一个应该
  • 虽然我同意这是当今人们应该编写此类代码的方式,但它并不能回答问题。
  • @user633183 这是一个很好的回应,但我的问题是关于 map 函数中回调的定义。我总是应该使用 promise 而不是回调。但是当我看到示例时,我找不到回调定义的位置
  • 在完整的例子中,asyncMap 提供了func 的回调;您可以在代码 sn-p 中将其视为(err, x) =&gt; ...。用户在呼叫站点提供的最终回调cb。这能回答你的问题吗?
猜你喜欢
  • 2017-11-18
  • 1970-01-01
  • 2021-02-20
  • 2018-11-17
  • 1970-01-01
  • 1970-01-01
  • 2015-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多