首先成功解决ES6承诺?

Tob*_*itt 19 ecmascript-6 es6-promise

我对ES6 Promise API中的某些东西感到困惑.我可以看到一个明确的用例,用于同时提交多个异步作业,并"解决"第一次成功.例如,这将服务于多个等效服务器可用的情况,但有些可能已关闭,而其他服务器负载较重且速度较慢,因此我的目标是从第一个服务器获得响应成功,并忽略其余服务器(是的,我知道这对于客户端从服务器的角度来看是一种令人讨厌的方式,但它对最终用户来说非常好;)

但是,据我所知,我有"全部"或"种族"行为."所有"行为似乎要等到所有请求都完成,这意味着我必须等待最慢,即使服务器已经完成(实际上,我可能要等待超时,这将是一场灾难对于这种情况.)然而,"种族"行为似乎让我第一次完成,如果这恰好是失败,也是一场灾难.

API中是否存在允许"raceToSuccess"行为的内容,或者我是否需要手动构建它.就此而言,我将如何手工制作它?

作为旁注,我在Java 8 CompletableFuture中发现了同样的难题,它似乎是一个紧密并行的API.那么,我在哲学层面上错过了一些东西吗?

log*_*yth 44

这是一个典型的例子,反转你的逻辑使它更清晰.在这种情况下,您的"竞争"是您希望您的拒绝行为实际上是成功行为.

function oneSuccess(promises){
  return Promise.all(promises.map(p => {
    // If a request fails, count that as a resolution so it will keep
    // waiting for other possible successes. If a request succeeds,
    // treat it as a rejection so Promise.all immediately bails out.
    return p.then(
      val => Promise.reject(val),
      err => Promise.resolve(err)
    );
  })).then(
    // If '.all' resolved, we've just got an array of errors.
    errors => Promise.reject(errors),
    // If '.all' rejected, we've got the result we wanted.
    val => Promise.resolve(val)
  );
}
Run Code Online (Sandbox Code Playgroud)

  • +1 跳出框框思考(颠倒逻辑),但这似乎很hacky 和**'无语义'**!把解决方案变成拒绝,反之亦然……它完成了工作,但哇,我的想法真的很棒。做得很好。 (3认同)
  • 再加上一个避免promise构造函数反模式的方法。 (2认同)
  • 是的,这是我在出现承诺问题时要注意的事情之一。如果您将 `new Promise` 用于将回调 API 转换为 Promise API 以外的任何其他用途,那么您需要_真的_充分的理由。 (2认同)
  • Gach,当你这么简单的时候就这么简单.感谢非常优雅的解决方案.我承认我仍然不禁认为它属于API! (2认同)
  • `Promise.all`和`Promise.race`本质上是`AND`和`OR`,因此你可以构建任何与这样的否定相结合所需的逻辑.在决定在API中包含什么时,很难知道何时绘制线,但这些至少是可靠的构建块. (2认同)

小智 6

你可以很容易地自己写这个.

function raceToSuccess(promises) {
  return new Promise(
    resolve => 
      promises.forEach(
        promise => 
          promise.then(resolve)
      )
  );
}
Run Code Online (Sandbox Code Playgroud)

这启动了所有的承诺,当任何成功解决了新的承诺及其价值.失败的承诺将被忽略.随后的成功承诺不会发生任何事情,因为新的承诺已经得到解决.请注意,如果没有任何输入promises解析,则生成的promise将永远不会解析或拒绝.

这是一个修改版本,如果所有输入promise都拒绝,则返回被拒绝的promise:

function raceToSuccess(promises) {
  let numRejected = 0;

  return new Promise(
    (resolve, reject) => 
      promises.forEach(
        promise => 
          promise . 
            then(resolve) .
            catch(
              () => {
                if (++numRejected === promises.length) reject(); 
              }
           )
       )
  );
}
Run Code Online (Sandbox Code Playgroud)

我喜欢@ loganfsmyth的方法; 你可能应该赞成它的概念清晰度.这是它的一个变种:

function invertPromise(promise) {
  return new Promise(
    (resolve, reject) => 
      promise.then(reject, resolve)
  );
}

function raceToSuccess(promises) {
  return invertPromise(
    Promise.all(
      promises.map(invertPromise)));
}
Run Code Online (Sandbox Code Playgroud)

另一个想法是将失败的承诺变成既不解决也不拒绝的承诺(换句话说,永久未决),然后使用Promise.race:

function pendingPromise()      { return new Promise(() => { }); }
function killRejected(promise) { return promise.catch(pendingPromise); }

function raceToSuccess(promises) {
  return Promise.race(promises.map(killRejected));
}
Run Code Online (Sandbox Code Playgroud)

你可能喜欢或不喜欢这种行为.如果没有任何输入承诺符合,则返回的承诺将永远不会实现或拒绝.永久未决的承诺也可能无法获得GC,或者某些引擎可能最终会抱怨它们.


Ada*_*ony 5

我正在使用基于 Promise.race() 的函数,但有一个变化:它忽略拒绝,除非所有给定的承诺都拒绝:

// ignores any rejects except if all promises rejects
Promise.firstResolve = function (promises) {
    return new Promise(function (fulfil, reject) {
        var rejectCount = 0;
        promises.forEach(function (promise) {
            promise.then(fulfil, () => {
                rejectCount++;
                if(rejectCount == promises.length) {
                    reject('All promises were rejected');
                } 
            });
        });
    });
};
Run Code Online (Sandbox Code Playgroud)

它基于 Rich Harris 的 Promise polyfill 竞赛方法。我刚刚将循环 Promise 拒绝设置为有条件的:如果所有给定的 Promise 都失败,它只会拒绝主要 Promise,否则它会忽略拒绝并解决第一个成功问题。

用法:

// fastest promise to end, but is a reject (gets ignored)
var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("foo")
    }, 100);
})

// fastest promise to resolve (wins the race)
var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("bar")
    }, 200);
})

// Another, slower resolve (gets ignored)
var promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("baz")
    }, 300);
})

Promise.firstResolve([promise1, promise2, promise3])
    .then((res) => {
        console.log(res) // "bar"
    })
    .catch(err => {
        console.log(err) // "All promises were rejected" (if all promises were to fail)
    })
Run Code Online (Sandbox Code Playgroud)

我使用这种方法而不是承诺反转方法的原因是因为在我看来这更具可读性。

为了以最严格的方式解决这个问题,下面有一个版本可以解决第一个成功的承诺,但如果所有给定的承诺都失败则不执行任何操作:

// ignores any and all rejects
Promise.firstResolve = function (promises) {
    return new Promise(function (fulfil) {
        promises.forEach(function (promise) {
            promise.then(fulfil, () => {});
        });
    });
};
Run Code Online (Sandbox Code Playgroud)

(用法同上)

编辑:这实际上与@user663031的建议相同。直到现在我才意识到这一点。


Cer*_*nce 5

API 中是否存在允许“raceToSuccess”类型行为的内容

现在有了。已完成的第四阶段提案如下Promise.any

Promise.any()接受 Promise 对象的可迭代对象,并且一旦可迭代对象中的一个承诺满足,就返回一个用该承诺的值解析的承诺。

因此,可以使用以下语法:

// assume getApi returns a Promise

const promises = [
  getApi('url1'),
  getApi('url2'),
  getApi('url3'),
  getApi('url4'),
];
Promise.any(promises)
  .then((result) => {
    // result will contain the resolve value of the first Promise to resolve
  })
  .catch((err) => {
    // Every Promise rejected
  });
Run Code Online (Sandbox Code Playgroud)

Promise.any已在所有现代浏览器中实现。也有一些 Polyfill可用。