调用一组函数,每个函数都会收到一个回调

时间:2017-04-16 08:24:26

标签: javascript ecmascript-6 async-await

我有一系列接受回调的功能,并且应该相互馈送,每个功能轮流,以及一个" major"也接受回调的函数。 this.app指的是一个类的成员(es6)。我希望用as6的现代工具替换async模块的异步调用:

firstFunction(next){
  if(this.app.isValid()) {
    next(null, app);
  } else {
    next(thia.app.sendError(), null)
  }
}

secondFunc(next){
    this.app.data = {
       reader: "someone"
    };
    next(null, this.app);
}

thirdFunc(next){
    next(null, this.app);
}

majorStuff(next){
    //USING async module (I don't want that)
    // async.series([
    //    firstFunction,
    //     secondFunction,
    //    thirdFunction
    // ], (err, result) => {
    //  if(err) {
    //      next({
    //          success: false,
    //          message: err
    //      })
    //  } else {
    //      next({
    //          success: true,
    //          message: "Welcome to Mars!"
    //      })
    //  }
    // });

    <using babel-polyfill, stage-0 + es2015 presets instead>
}

4 个答案:

答案 0 :(得分:2)

你可以简单地模仿async.series界面:

function series(fns, cb) {
  const results = [];

  const s = fns.map((fn, index) => () => {
    fn((err, result) => {
      if (err) return cb(err, null);
      results.push(result);
      if (s[index + 1]) return setImmediate(s[index + 1]);
      return cb(null, results);
    });
  });

  s[0]();
}

然后这样称呼:

series([
  first,
  second,
  third
], (err, results) => console.log(err, results));

答案 1 :(得分:1)

  

我有一系列接受回调的函数,应该互相反馈,每个函数轮流

但是你以愚蠢的方式编写了你的​​功能。如果每个人只接受回调,他们如何提供?为了从一个函数创建一个通用的数据流到另一个函数,每个函数都需要以统一的方式编写。我们先来看看你的功能

// only accepts a callback
firstFunction(next){
  // depends on context using this
  if(this.app.isValid()) {
    // calls the callback with error and successful value
    next(null, app);
  } else {
    // calls the callback with error and successful value
    next(this.app.sendError(), null)
  }
}

我们想要使这个通用,以便我们可以在链中组装许多功能。也许我们可以提出一些看起来像这样的界面

// where `first`, `second`, and `third` are your uniform functions
const process = cpscomp (first, second, third)

process(app, (err, app) => {
  if (err)
    console.error(err.message)
  else
    console.log('app state', app)
})

这个答案存在,如果有的话,向你展示用连续传递方式写作的工作量 - 也许更重要的是,使用Promises的工作量可以为你节省多少。这并不是说CPS没有用例,只是它可能不应该成为异步控制流的首选。

婴儿步骤

我喜欢使用最少的代码来处理这些东西,所以我可以看到所有内容如何组合在一起。下面我们有3个示例函数(firstsecondthird)和一个将它们链接在一起的函数,compcps(代表撰写继续传递式

const first = (x, k) => {
  k(x + 1)
}

const second = (x, k) => {
  k(x * 2)
}

const third = (x, k) => {
  k(x * x * x)
}

const compcps = (f, ...fs) => (x, k) => {
  if (f === undefined)
    k(x)
  else
    f(x, y => compcps (...fs) (y, k))
}

const process = compcps (first, second, third)

process(1, x => console.log('result', x))
// first(1, x => second(x, y => third(y, z => console.log('result', z))))
// second(2, y => third(y, z => console.log('result', z)))
// third(4, z => console.log('result', z))
// console.log('result', 64)
// result 64

节点式延续传递

Node通过首先将Error(如果存在)传递给回调来在此基础上添加一层约定。为了支持这一点,我们只需对compcps函数进行微小更改 - (粗体更改)

const compcps = (f,...fs) => (x, k) => {
  if (f === undefined)
    k(null, x)
  else
    f(x, (err, y) => err ? k(err, null) : compcps (...fs) (y, k))
}

const badegg = (x, k) => {
  k(Error('you got a real bad egg'), null)
}

const process = compcps (first, badegg, second, third)

process(1, (err, x) => {
  if (err)
    console.error('ERROR', err.message)
  else
    console.log('result', x)
})
// ERROR you got a real bad egg

错误直接传递给我们的process回调,但我们必须小心!如果有一个疏忽函数抛出错误但没有将它传递给回调的第一个参数怎么办?

const rottenapple = (app, k) => {
  // k wasn't called with the error!
  throw Error('seriously bad apple')
}

让我们对compcps函数进行最后更新,以便将这些错误合理地汇集到回调中,以便我们能够正确处理它们 - (粗体更改)

const compcps = (f,...fs) => (x, k) => {
  try {
    if (f === undefined)
      k(null, x)
    else
      f(x, (err, y) => err ? k(err, null) : compcps (...fs) (y, k))
  }
  catch (err) {
    k(err, null)
  }
}

const process = compcps (first, rottenapple, second, third)

process(1, (err, x) => {
  if (err)
    console.error('ERROR', err.message)
  else
    console.log('result', x)
})
// ERROR seriously bad apple

在代码中使用compcps

现在你知道你的功能必须如何构建,我们可以轻松地编写它们。在下面的代码中,我将传递this作为从一个函数移动到另一个函数的状态,而不是依赖于上下文相关的app。正如您在compcps中看到的那样,可以使用单个main调用很好地表达整个函数序列。

最后,我们运行main两个不同的状态来查看不同的结果

const compcps = (f,...fs) => (x, k) => {
  try {
    if (f === undefined)
      k(null, x)
    else
      f(x, (err, y) => err ? k(err, null) : compcps (...fs) (y, k))
  }
  catch (err) {
    k(err, null)
  }
}

const first = (app, k) => {
  if (!app.valid)
    k(Error('app is not valid'), null)
  else
    k(null, app)
}

const second = (app, k) => {
  k(null, Object.assign({}, app, {data: {reader: 'someone'}}))
}

const third = (app, k) => {
  k(null, app)
}

const log = (err, x) => {
  if (err)
    console.error('ERROR', err.message)
  else
    console.log('app', x)
}

const main = compcps (first, second, third)
  
main ({valid: true}, log)
// app { valid: true, data: { reader: 'someone' } }

main ({valid: false}, log)
// ERROR app is not valid

<强>说明

正如其他人所评论的那样,您的代码只是在做同步事情。我确定你已经过度简化了你的例子(你不应该这样做),但我在这个答案中提供的代码可以完全异步运行。每当调用k时,序列将移至下一步 - 同步或异步调用k

所有事情都说,延续传递风格并非没有头疼。会遇到很多小陷阱。

  • 如果从未调用回调怎么办?我们如何调试问题?
  • 如果多次调用回调怎么办?

许多人已经开始使用Promises来处理异步控制流;特别是因为他们已经快速,稳定并且本地支持Node已经有一段时间了。 API当然是不同的,但它旨在减轻大量使用cps时存在的一些压力。一旦你学会使用Promises,他们就会感觉很自然。

此外,async/await是一种新语法,它大大简化了使用Promises附带的所有样板 - 最后异步代码可以非常平坦,就像它的同步代码一样。

对Promises的方向有很大的推动,社区背后也是如此。如果你不熟悉CPS,那么掌握一些技巧是很好的,但是如果你正在编写一个新的应用程序,我会放弃CPS而不是以后再使用Promises API。

答案 2 :(得分:0)

如果您的函数是异步的,那么请考虑通过函数生成器进行协调:

// Code goes here
var app = {};

function firstFunction(){
  if(isValid(app)) { 
    setTimeout(function(){
      gen.next(app); 
    }, 500);
  } else {
    gen.next(null); 
  }
  function isValid(app) {
    return true;
  }
}

function secondFunc(app){
    setTimeout(function(){
      app.data2 = +new Date();
      gen.next(app); 
    }, 10);
}

function thirdFunc(app){
    setTimeout(function(){
      app.data3 = +new Date();
      gen.next(app); 
    }, 0);
}

function* majorStuff(){
  var app = yield firstFunction();
  app = yield secondFunc(app);
  app = yield thirdFunc(app);
  console.log(app);
}

var gen = majorStuff();
gen.next();

答案 3 :(得分:-2)

基本上只看一下这个例子,我认为没有理由使用任何异步相关的东西。但是如果你想使用async - await重现这一点,那么这是一种方法:

首先转换您的方法,以便它们返回Promise。 Prom可以使用值解析,也可以使用错误拒绝。

const firstFunction() {
  return new Promise((resolve, reject) => {
    if(this.app.isValid()) {
      resolve(app)
    } else {
      // assuming sendError() returns the error instance
      reject(thia.app.sendError()) 
    }
  })
}

secondFunc() {
  return new Promise(resolve => {
    this.app.data = {
      // its not a good idea to mutate state and return a value at the same time
      reader: "someone"  
    }
    resolve(this.app)
  })
}

thirdFunc(){
  return new Promise(resolve => resolve(this.app))  
}

既然你有你的承诺返回函数,你可以在异步函数中等待它们:

async majorStuff() {
  try {
    await Promise.all(
      this.firstFunction(),
      this.secondFunc(),
      this.thirdFunc()
    )
    return { success: true, message: "Welcome to Mars!" }
  } catch(e) {
    return { success: false, message: e.message }
  }
}

或者将它们用作常规承诺:

const result = Promise.all(
  this.firstFunction(),
  this.secondFunc(),
  this.thirdFunc()
).then(() => ({ success: true, message: "Welcome to Mars!" }))
 .catch(e => ({ success: false, message: e.message }))

如果您希望外部API能够挂钩您的方法,那么您现在可以使用这些可组合的部分来执行此操作。

如果你想确保你的Promise按顺序运行,你可以这样做:

const runSeries = (promiseCreators, input) => {
  if (promiseCreators.length === 0) {
    return Promise.resolve(input)
  }
  const [firstCreator, ...rest] = promiseCreators
  return firstCreator(input).then(result => runSeries(rest, result))
}

runSeries([
  input => Promise.resolve(1 + input),
  input => Promise.resolve(2 + input),
  input => Promise.resolve(3 + input),
], 0).then(console.log.bind(console)) // 0 + 1 => 1 + 2 => 3 + 3 => 6

函数runSeries接受一组promise-creator(返回promise的函数)并从给定的输入开始运行它们,然后运行先前运行的promise的结果。这与async.series一样接近。显然,您可以根据需要调整它以更好地处理参数。