【问题标题】:Decorate Javascript Promise.then so that the argument function receive additional parameter装饰 Javascript Promise.then 以便参数函数接收附加参数
【发布时间】:2016-10-03 10:12:37
【问题描述】:

我有以下使用 ES6 Promise 的代码:

const ctx = {}
Promise.resolve()
  .then(() => doSomethingWith(ctx))
  .then((retValue) => doSomethingElseWith(retValue, ctx))

我希望能够做这样的事情:

const ctx = {}
using(ctx)
   .then((ctx) => doSomethingWith(ctx))
   .then((retValue, ctx) => doSomethingElseWith(retValue, ctx))

对于第一个then,我有这样的事情:

function using(ctx) {
  const p = Promise.resolve()
  p.then = (fn) => withCtx(fn, ctx)
  return p
}

function withCtx (fn, ctx) {
  const p = new Promise((resolve) => {
    resolve(fn.apply(this, [ctx]))
  })
  return p
}

但我希望它能够处理第二个的情况,它同时接受先前的承诺返回值和上下文......我怎么会有这部分:

fn.apply(this, [ctx]) // want previous promise returned value and ctx here !

处理这种情况 fn 已经采用了我们想要传播的参数......上面描述的retValue 情况。

有什么想法吗? (我想在之后介绍的用例是能够随时停止 Promise 链,将值保存在磁盘上并随时重新启动,并从磁盘恢复更改的上下文)

【问题讨论】:

  • ctx 是否与回调函数在同一个范围内?还是更像return using(ctx);then 回调将附加到其他地方......?
  • ctx 不能被视为始终在范围内...我希望能够环绕传递给 then 的函数,以便我可以处理/更改 retValuectx以 AOP 方式
  • 您始终可以编写自己的 Promise 实现 - 因为您无法更改“本机”实现您的出价
  • “你总是可以编写你自己的 Promise 实现” - 我不认为这是一个明智的建议。编写一个 Promise 实现并不是一件容易的事——而且它已经完成了。只需使用现有的。
  • 您可以查看绑定的承诺,就像 bluebird 提供的那样:bluebirdjs.com/docs/api/promise.bind.html。也许重写你的函数,使它们不依赖于外部范围状态也是一种选择?

标签: javascript node.js promise es6-promise


【解决方案1】:

老实说,我认为这很令人困惑,并且可能是一种反模式,但是您可以通过添加一个额外的 then 在 Promise 之间传递可变状态吗?

Promise.resolve({}) 
.then(ctx => doSomethingWith(ctx)
  .then(ret => ({ret, ctx})))
.then(({ret, ctx}) => doSomethingElseWith(ret, ctx))

第二个then 在第一个内部,因此对输入状态有一个闭包,它可以在将变异的上下文传递到下一阶段之前用转换后的值进行装饰。这在不泄露对全局上下文的任何引用的情况下实现。

显示扩展模式后的样子...

function trans (that, y) {
  console.dir(y);
  return new Promise(res =>
    setTimeout(res.bind(null, that.op(y)), 1000)
  )
}

Promise.resolve({ret: 0, ctx: {op: _ => _ + 2}})
  .then(({ret, ctx}) => trans(ctx, ret)
    .then(ret =>({ ret, ctx}))
  )
  .then(({ret, ctx}) => trans(ctx, ret)
    .then(ret =>({ ret, ctx}))
  )
  .then(({ret, ctx}) => trans(ctx, ret)
    .then(ret =>({ ret, ctx}))
  )
  .then(({ret, ctx}) => trans(ctx, ret)
    .then(ret =>({ ret, ctx}))
  )
  .then(ctx => console.dir(ctx));
  .catch(console.log.bind(console));

【讨论】:

  • 这不是反模式。你甚至不需要真正改变ctx,你也可以传递一个新的(克隆的)
  • @Bergi,感谢您的编辑。我没有在函数参数中使用解构赋值,因为它会抛出节点(5.12)。受您编辑的启发,我在 Chrome v53 中对其进行了测试,它确实有效。
【解决方案2】:

以下可能是您正在寻找的模式:

something0()
  .then(result0 => something1(ctx, result0)
    .then(result1 => something2(ctx, result0, result1)
      .then(result2 => something3(ctx, result0, result1, result2))
    )
  )
;

是的,这是一种末日金字塔,但没有其他方法可以将先前的结果保持在范围内,以供以后使用。

【讨论】:

    【解决方案3】:

    值得记住的是,如果您需要通过计算传递多个值,那么您可以简单地从 .then 函数返回一个包含多个值/结果的列表(数组):

    Promise.resolve(6)
      .then( x => [x+6, x] )
      .then( ([latestVal, oldvalue]) => /*whatever*/ );
    

    这种方法没有任何问题,并且还有各种其他方法(例如unapply/apply,您可以使用它来修改处理函数,以便接收/使用数组作为返回类型(您应该真正将其视为 嵌套的 类型:您现在正在处理 Promise[Array[...values]] 这实际上是非常常见的模式。

    请注意,即使某些值本身是从异步操作派生的,这仍然有效:只需将它们传递给 Promise.all,它会将值/承诺的数组转换为数组的 Promise,然后您就可以解构Array 以获得各种结果。

    Promise.resolve(6)
      .then( x => Promise.all([additionApi(x), x]) )
      .then( ([latestVal, ...rest]) => Promise.all( additionApi(x).concat(latest, rest) ) )
      .then( ([latestVal, ...rest]) => Promise.all( additionApi(x).concat(latest, rest) ) );
      //...etc.
    

    【讨论】:

      猜你喜欢
      • 2016-04-11
      • 2013-09-14
      • 2014-12-28
      • 2020-03-04
      • 1970-01-01
      • 2014-07-21
      • 2015-09-03
      • 1970-01-01
      • 2015-08-14
      相关资源
      最近更新 更多