【问题标题】:How to return a value and a promise from a function at the same time?如何同时从函数返回值和承诺?
【发布时间】:2016-02-24 23:15:27
【问题描述】:

我正在做这样的事情

var command1;
var command2;

var fn = function(param) {
  var deferred = Q.defer();
  var command = spawn(..., [
    ... passing different arguments based on param ...
  ]);
  ...
  command.stdout.on('data', function(data) {
    if (/... if process started successfully .../.test(data)) {
      deferred.resolve();
    }
  });
  ...
  if (param === 'command1') {
     command1 = command;
  } else {
     command2 = command;
  }
  return deferred.promise;
};

Q.all([
  fn('command1'),
  fn('command2')
]);

后来我打电话给command1.kill()command2.kill()。我考虑过将command 传递给resolve,但它可能永远不会被调用。我也可以将command 传递给reject,这样如果出现问题,我可以在那里打电话给kill,但这感觉很奇怪。

我如何以惯用的方式向调用者返回command 和承诺?没有fn 中的条件部分。有哪些可能性?

我也想过 ES6 的解构赋值特性,但考虑以下

  ...
  return [command, deferred.promise];
}

[command1, promiseCommand1] = fn('command1');
[command2, promiseCommand2] = fn('command2');

Q.all([
  promise1,
  promise2.then(Q.all([
    promiseCommand1,
    promiseCommand2    
  ])
]);

但这失败了(至少在我的特定情况下,命令应该等到promise2 解决),因为当我将promiseCommand1promiseCommand2 传递给Q.all 时,这些进程已经在路上了。

不确定我是否使用了正确的解构赋值语法。

突然出现在我的脑海里

var command1;
var command2;

var fn = function(param, callback) {
  var deferred = Q.defer();
  var command = spawn(..., [...]);
  ...
  callback(command);
  return deferred.promise; 
};

Q.all([
  fn('command1', function(command) {
    command1 = command;
  }),
  fn('command1', function(command) {
    command2 = command;
  })
]);

还有其他方法吗?

更新

从昨天开始我就想出了如何使用解构赋值(仍然不确定语法)

Q.all([
  promise1,
  promise2.then(function() {
    [command1, promiseCommand1] = fn('command1');
    [command2, promiseCommand2] = fn('command2');    
    return Q.all([
      promiseCommand1,
      promiseCommand2    
    ]);
  })
]);

这样命令只会在promise2被解析后执行。

解决方案

基于the accepted answer 和我之前的更新我想出了这个

  command.promise = deferred.promise;
  return command;
};

Q.all([
  promise1,
  promise2.then(function() {
    command1 = fn('command1');
    command2 = fn('command2');    
    return Q.all([command1.promise, command2.promise]);
  })
]);

有效,对我来说似乎是一个简洁的解决方案。我不想依赖 ES6 进行解构分配。另外,我认为我不能使用该功能将一个值分配给在范围之外声明的变量,并在本地范围内简明地分配另一个值。返回

return {
  command: command,
  promise: deferred.promise
};

也是一种可能的解决方案,但不太简洁。

Q.all([
  promise1,
  promise2.then(function() {
    var result1 = fn('command1');
    var result2 = fn('command2');
    command1 = result1.command;
    command2 = result2.command;   
    return Q.all([result1.promise, result2.promise]);
  })
]);

更正

在已接受答案的评论部分,建议我在fn 中调用reject,以防止我的代码因为未决的承诺而永远挂起。我已经用以下方法解决了这个问题

  command.promise = deferred.promise.timeout(...);
  return command;
};

使用timeout 将返回相同的promise,但是如果promise 没有在给定的超时值内解决,promise 将被自动拒绝。

【问题讨论】:

  • promise2.then(Q.all([ 的问题在于 .then 的参数必须是函数 - 否则它们会被有效地忽略
  • @JaromandaX 是的,我的例子已经关闭。但是它很有教育意义:)

标签: javascript node.js promise q


【解决方案1】:

你应该把你的“通过命令拒绝”放在头上,从而得到一些有用的东西。换句话说,拒绝响应kill() 命令。

如您所知,问题在于fn() 应该返回一个promise,而promise 并不能自然地传达相应命令的.kill() 方法。但是,javascript 允许将属性(包括函数(作为方法))动态附加到对象。因此添加.kill() 方法很简单。

var fn = function(param) {
    var deferred = Q.defer();
    var command = spawn(..., [
        ... passing different arguments based on param ...
    ]);
    ...
    command.stdout.on('data', function(data) {
        if (/... if process started successfully .../.test(data)) {
            deferred.resolve();
        }
    });
    ...
    var promise = deferred.promise;
    // Now monkey-patch the promise with a .kill() method that fronts for command.kill() AND rejects the Deferred.
    promise.kill = function() {
        command.kill();
        deferred.reject(new Error('killed')); // for a more specific error message, augment 'killed' with something unique derived from `param`. 
    }
    return promise;
};

var promise1 = fn('command1');
var promise2 = fn('command2');

Q.all([promise1, promise2]).spread(...).catch(...);

promise1.kill()promise2.kill() 将导致“killed”在 catch 处理程序中显示为 error.message

两个kill可以酌情调用,例如...

if(...) {
    promise1.kill();
}
if(...) {
    promise2.kill();
}

另外,.kill() 方法也将干净地分离,而无需 .bind(),例如:

doSomethingAsync(...).then(...).catch(promise1.kill);
doSomethingElseAsync(...).then(...).catch(promise2.kill);

请注意,fn() 将适用于任意数量的调用,而无需外部变量 command1command2 等。

【讨论】:

  • 好主意,但我认为我做不到。我没有在问题中提到它(认为这无关紧要,但实际上确实如此),但我在 Protractor 集成测试的 beforeLaunch 函数中启动命令,我在 @987654338 中调用 kill @。我从beforeLaunch 返回,并承诺延迟测试的开始,直到我的进程启动并运行。在afterLaunch 中,chain 无法再访问(至少,我认为无法访问它)。
  • 刚来找我。对我来说,解决方案几乎与您提出的相同,但我在promise 上修补了kill,而是在ChildProcess 对象上修补了promise。我将更新我的答案以包含解决方案。感谢您的想法!
  • 无论您使用哪种方式进行修补,我仍然认为您需要一种机制来在调用 .kill() 时拒绝 Deferred。否则,您的 Promise.all([..., ...]) 将在发出 kill 命令后保持“待处理”状态。
  • 如果我在ChildProcess 上呼叫kill,这意味着resolve 被呼叫。如果它没有被调用,那么要么我已经调用reject 监听进程的“错误”事件,或者......我无法决定是否应该监听stdoutstderr reject。如果从未调用过resolve,Protractor 将超时并导致整个测试失败并出现错误,所以这对我来说很好。
  • 目前deferred.reject();没有出现在fn()中,因此fn()返回的promise不会超时。
【解决方案2】:

您可以返回一个数组,然后使用promise.spread 方法。

https://github.com/kriskowal/q#combination

.then(function () {
    return [command, promise];
})
.spread(function (command, promise) {
});

【讨论】:

  • 如果我从fn 返回[command, deferred.promise];,那么该函数将不会返回一个promise,而是一个第二个元素是promise 的数组。要使用spread,我必须解析为一个数组,不是吗? deferred.resolve([...]) 那么我可以使用spread。无论如何,对不起,我不明白你的意思。你能详细说明一下吗?
猜你喜欢
  • 2017-04-27
  • 2019-01-23
  • 2017-04-05
  • 2020-08-26
  • 2020-10-11
  • 2014-09-22
  • 2014-07-05
  • 1970-01-01
相关资源
最近更新 更多