如何同时 return 一个值和一个函数的承诺?

How to return a value and a promise from a function at the same time?

我正在做这样的事情

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,但这感觉很奇怪。

我如何 return 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 被解析),因为当我通过 promiseCommand1promiseCommand2Q.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 解决后执行。

解决方案

基于和我之前的更新,我想到了这个

  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 将 return 相同的承诺,但是如果在给定的超时值内未解决承诺,承诺将被自动拒绝。

你可以return一个数组,然后使用promise.spread方法。

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

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

你应该把你的 "pass command to reject" 颠倒过来,得到一些有用的东西。换句话说,拒绝响应 kill() 命令。

如您所知,问题在于 fn() 应该 return 承诺,而承诺不会自然地传达相应命令的 .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" 在捕获处理程序中显示为 error.message

二杀可以酌情调用,比如...

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

或者,.kill() 方法也将完全分离,无需 .bind(),例如:

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

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