首先成功解决ES6承诺?

时间:2016-05-15 03:19:14

标签: ecmascript-6 es6-promise

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

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

API中是否存在允许" raceToSuccess"一种行为,还是我必须手工制作它。就此而言,我将如何手工制作它?

作为旁注,我在Java 8 CompletableFuture中发现了同样的难题,它似乎是一个紧密并行的API。那么,我在哲学层面上遗漏了什么吗?

7 个答案:

答案 0 :(得分:36)

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

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)
  );
}

答案 1 :(得分:6)

你可以很容易地写这个。

function raceToSuccess(promises) {
  return new Promise(
    resolve => 
      promises.forEach(
        promise => 
          promise.then(resolve)
      )
  );
}

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

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

function raceToSuccess(promises) {
  let numRejected = 0;

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

我喜欢@ loganfsmyth的方法;你可能应该赞成它的概念清晰度。以下是它的变体:

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

function raceToSuccess(promises) {
  return invertPromise(
    Promise.all(
      promises.map(invertPromise)));
}

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

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

function raceToSuccess(promises) {
  return Promise.race(promises.map(killRejected));
}

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

答案 2 :(得分:1)

老话题,但这是我的参赛作品;它本质上是@ loganfsmyth的解决方案,但还有一些检查符合Promise.all()建立的约定:

  • 作为输入的空数组返回(同步)已解决的承诺
  • 数组中的非承诺条目导致第一个此类条目用作已解析值



Promise.any = a => {
  return !a.length ?
    Promise.resolve() :
    Promise.all(a.map(
      e => (typeof e.then !== 'function') ?
        Promise.reject(e) :
        e.then(
          result => Promise.reject(result),
          failure => Promise.resolve(failure)
        )
    )).then(
      allRejected => Promise.reject(allRejected),
      firstResolved => Promise.resolve(firstResolved)
    );
};

// Testing...

function delayed(timeout, result, rejected) {
  return new Promise((resolve, reject) => {
    setTimeout(
      () => rejected ? reject(result) : resolve(result),
      timeout);
  });
}

Promise.any([
  delayed(800, 'a'),
  delayed(500, 'b'),
  delayed(250, 'c', true)
]).then(e => {
  console.log('First resolved (expecting b):', e);
});

Promise.any([
  delayed(800, 'a', true),
  delayed(500, 'b', true),
  delayed(250, 'c', true)
]).then(null, e => {
  console.log('All rejected (expecting array of failures):', e);
});

Promise.any([
  delayed(800, 'a'),
  delayed(500, 'b'),
  delayed(250, 'c', true),
  'd',
  'e'
]).then(e => {
  console.log('First non-promise (expecting d):', e);
});

// Because this is the only case to resolve synchronously,
// its output should appear before the others
Promise.any([]).then(e => {
  console.log('Empty input (expecting undefined):', e);
});




答案 3 :(得分:1)

我使用的是基于Promise.race()的函数,但有一个转折:除非所有给定的promise都拒绝,否则它将忽略拒绝:

// 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');
                } 
            });
        });
    });
};

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

用法:

// 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)
    })

之所以使用此方法而不是使用承诺倒置方法,是因为我认为这更具可读性。

为了以最严格的方式解决这个问题,下面有一个版本可以解决第一个成功的诺言,但是如果所有给定的诺言都失败了,则什么都不做:

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

(用法与上面相同)

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

答案 4 :(得分:1)

API中是否有某些东西允许“ raceToSuccess”行为

很快,几乎肯定会有。 proposal有第3阶段Promise.any

: asum array([18, 27, 23, 18, 26, 34, 24, 23, 15, 30, 16, 26, 21, 24, 16, 21, 24, 29, 23, 23]) idxs[np.argsort(asum[idxs])][::-1][:2] array([ 5, 17]) 接受一个Promise对象的可迭代对象,并且一旦可迭代对象中的一个Promise满足,就返回一个单独的Promise,并使用该Promise中的值进行解析。

因此,以下语法将有效:

Promise.any()

// 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 }); 已经implemented in Spidermonkey,并且有some polyfills可用。

答案 5 :(得分:0)

我通过超时扩展了@loganfsmyth方法,并编写了一个小函数:

  • 运行所有承诺,
  • 等待承诺成功的时间不超过指定的时间(options.timeOutMs),
  • 返回成功的第一个。

在以下代码段中,您可以对其进行测试:



const firstThatCompleteSuccessfullyES6 = (options) => {

    // return the first promise that resolve
    const oneSuccess = (promises) => 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) => { return Promise.reject(val); },
                        (err) => { return Promise.resolve(err); }
                    );
            })
            ).then(
                // If '.all' resolved, we've just got an array of errors.
                (errors) => { return Promise.reject(errors); },

                // If '.all' rejected, we've got the result we wanted.
                (val) => { return Promise.resolve(val); }
            );
    

    // return the promise or reect it if timeout occur first
    const timeoutPromise = (ms, promise) => new Promise(function(resolve, reject) {
            setTimeout(() => reject(new Error('timeout')), ms);
            promise.then(resolve, reject);
        });
    

    if (options.subsystems.length < 1) {
        return Promise.reject('Parameters error, no subSystems specified');
    }

    const timedOutSubsystems = options.subsystems.map(function(subsystem){
        return timeoutPromise(options.timeOutMs, subsystem(options));
    });

    const startDate = Date.now();

    return oneSuccess(
        timedOutSubsystems
    )
    .then((result) => {
        const elapsedTime = Math.abs((startDate - Date.now()) / 1000);
        console.log('firstThatCompleteSuccessfully() done, after s: ' + elapsedTime + ': '+ result);
        return result;
    })
    .catch((error) => {
        const elapsedTime = Math.abs((startDate - Date.now()) / 1000);
        console.error('firstThatCompleteSuccessfully() error/nodata: ' + error);
    });

}



// example of use with two promises (subsystem1 & subsystem2) that resolves after a fixed amount of time

const subsystem1 = (options) => new Promise(function(resolve, reject) {
        setTimeout(function(){
            console.log('subsystem1 finished');
            resolve('subsystem 1 OK');
        }, 1000);
    });



const subsystem2 = (options) => new Promise(function(resolve, reject) {
        setTimeout(function(){
            console.log('subsystem2 finished');
            resolve('subsystem 2 OK');
        }, 2000);
    });


firstThatCompleteSuccessfullyES6({
    subsystems: [subsystem1, subsystem2],
    timeOutMs: 2000
})
.then((result) => console.log("Finished: "+result));
&#13;
&#13;
&#13;

答案 6 :(得分:0)

为解决此问题,我使用了Promise.ricePromise.allSettled

下一个代码等待Promise.rice成功值。但是如果没有干草,那就成功了。返回所有错误的数组。

const PromiseRiceSuccess = <T = unknown>(promises: Promise<T>[]) => {
  let done: (reason?: T) => void;
  const waitEndAllPromises = new Promise((resolve, reject) => done = reject);
  const waitCatchs = promise => Promise.resolve(promise).catch(() => waitEndAllPromises);

  Promise.allSettled(promises).then(r => done(r));

  return Promise.race(promises.map(waitCatchs));
};

示例:

PromiseRiceSuccess([
  Promise.reject(1),
  new Promise((r) => setTimeout(() => r(2), 4000)),
]);
// 2

PromiseRiceSuccess([
  Promise.reject(1),
  new Promise((resolve, reject) => setTimeout(() => reject(2), 4000)),
]);
// Uncaught (in promise) (2) [{…}, {…}]