【问题标题】:Clean way to wait for first true returned by Promise等待 Promise 返回的第一个 true 的干净方式
【发布时间】:2018-12-12 02:28:54
【问题描述】:

我目前正在做一些事情,我在一个数组中发出三个 Promise。目前它看起来像这样

var a = await Promise.all([Promise1(), Promise2(), Promise3()]);

现在所有这些承诺要么返回真要么假。但目前我正在等待所有这些都完成,一旦其中一个返回 true,我就可以继续。

我想了一些方法来实现这一点,但似乎都很难看。你将如何解决这个任务?

【问题讨论】:

  • 你不能简单地使用Promise.race吗?
  • @user753642 我不这么认为。 Race 意味着,总是在一个 Promise 完成后停止。只有当任何一个 Promise 是 true 时,OP 才想停止。至少,这是我的理解。
  • 正确。感谢您的快速和许多答案。我想我得解决这个问题。看起来很有希望
  • 作为对此的更新,Promise.any() 可能会成为您想要的,一旦它到达常青浏览器。

标签: javascript node.js promise


【解决方案1】:

您可以创建一个new promise,只要任何给定的承诺解析为true,它就会立即解析,如下所示:

function promiseRaceTrue(promises) {
    return new Promise(function(resolve, reject) {
        promises.forEach(promise =>
            promise.then(val => val === true && resolve())
            // TODO handle resolve with value of "false"?
            // TODO handle rejection?
        );
        // TODO handle all resolved as "false"?
    });
}

var a = await promiseRaceTrue([Promise1(), Promise2(), Promise3()]);

它的行为类似于 Promise.race,但仅在给定的 Promise 之一以值 true 解析时才解析,而不是在任何给定的 Promise 解析或拒绝时立即解析。

【讨论】:

  • 如果他们都返回false会发生什么?承诺永远不会解决吗?
  • 使用当前代码,不,它不会解决。但是你可以通过添加Promise.all 轻松解决这个问题,如果所有给定的承诺都解析为false,则可以解决或拒绝。此外,您可能需要处理拒绝,但问题中未指定。
  • 我相信唯一的其他“干净”方法可能是将这个函数添加到Promise.prototype;但是,这可能是不干净的,因为它会编辑原型。
  • @Tyler 是的,“干净的方式”和“将此功能添加到Promise.prototype”在我看来是矛盾的;)
【解决方案2】:

可以使用Promise.race(),并且只有在值为真时才解析初始承诺

const doSomething = (bool, val)=>{
  return new Promise((resolve, reject)=>{
     if (bool){
        resolve(val)
     }
  })
}

const promise1 = doSomething(false,'one')
const promise2 = doSomething(false,'two')
const promise3 = doSomething(true,'three')
const promise4 = doSomething(true,'four')
const promise5 = doSomething(true,'five')

Promise.race([promise1, promise2, promise3, promise4, promise5]).then(value => {
  console.log('First true one to resolve is: ', value);
  
});

【讨论】:

    【解决方案3】:

    您可以结合Promise.racePromise.all 来实现:

    function firstTrue(promises) {
        const newPromises = promises.map(p => new Promise(
            (resolve, reject) => p.then(v => v && resolve(true), reject)
        ));
        newPromises.push(Promise.all(promises).then(() => false));
        return Promise.race(newPromises);
    }
    

    测试上述代码:

    function firstTrue(promises) {
      const newPromises = promises.map(p => new Promise(
        (resolve, reject) => p.then(v => v && resolve(true), reject)
      ));
      newPromises.push(Promise.all(promises).then(() => false));
      return Promise.race(newPromises);
    }
    
    var test = values => firstTrue(
      values.map((v) => new Promise((resolve) => {
        setTimeout(() => resolve(v), Math.round(Math.random() * 1000));
      }))
    ).then((ret) => console.log(values, ret));
    
    test([true, true, true]);
    test([false, false, false]);
    test([true, false, false]);
    test([false, true, false]);
    test([false, false, true]);

    【讨论】:

    • 这实际上可以解决如果所有这些都解析为 false 会发生什么的问题。
    • 我认为这是最苗条的方式。接受答案?你怎么看?
    • 这里没有竞争条件吗?如果所有的 Promise 同时解析为 true(或者在调用 firstTrue() 之前已经解析为 true),那么单个转换后的 Promise 不会与 .all() Promise 竞争吗?我认为用这个替换 .all() 承诺会解决它:Promise.all(promises).then(values => values.some(x => !!x))
    • @RichardHansen 的 Promise.then 分辨率在内部排队(此行为是 EcmaScript 标准所要求的)。 Promise.all 在内部调用 Promise.then 传递给它的承诺。然后需要在 Promise.all 返回的 Promise 上调用一个额外的 Promise.then。因此,Promise.all 的解析至少需要两个入队操作,而数组中的其他 Promise 只需要一个(p.then)。 ES 要求这些入队操作按照它们被调度的顺序被调用。所以最终的解析总是会在 Promise.all 的解析之前被调度。
    【解决方案4】:

    如果您想要最先进的解决方案,听起来您想要Promise.any() 哪个:

    Promise.any() 采用 Promise 对象的可迭代对象,并且只要 可迭代中的一个promise fulfils,返回一个promise 用该承诺的价值解决。如果没有承诺 可迭代的完成(如果所有给定的承诺都被拒绝),那么 返回的承诺被 AggregateError 拒绝,一个新的子类 Error 将单个错误组合在一起。本质上,这 方法与Promise.all()相反。

    但是,直到

    • 铬 85
    • 火狐79
    • Safari 14
    • 不支持 IE、Edge 或 Opera

    你基本上想要some()

    • Promise.all() 不起作用,因为它会让你等待所有 Promise 完成。
    • Promise.race() 单独不起作用,因为它只返回 1 Promise 的分辨率。

    相反,我们需要接收一个 Promise 数组并继续比赛,直到其中一个返回 true,此时我们应该停止。如果它们都不是真的,我们仍然需要停下来,但我们需要注意它们都不是真的。

    考虑以下带有测试工具的示例:

    代码

    /**
     * Promise aware setTimeout based on Angulars method
     * @param {Number} delay How long to wait before resolving
     * @returns {Promise} A resolved Promise to signal the timeout is complete
     */
    function $timeout(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }
    
    /**
     * Return true (early) if any of the provided Promises are true
     * @param {Function(arg: *): Boolean} predicate The test the resolved Promise value must pass to be considered true
     * @param {Promise[]} arr The Promises to wait on
     * @returns {Promise<Boolean>} Whether one of the the provided Promises passed the predicate
     */
    async function some(predicate, arr) {
        // Don't mutate arguemnts
        const arrCopy = arr.slice(0);
    
        // Wait until we run out of Promises
        while(arrCopy.length){
            // Give all our promises IDs so that we can remove them when they are done
            const arrWithIDs = arrCopy.map((p, idx) => p.then(data => ({idx, data})).catch(_err => ({idx, data: false})));
            // Wait for one of the Promises to resolve
            const soon = await Promise.race(arrWithIDs);
            // If it passes the test, we're done
            if(predicate(soon.data))return true;
            // Otherwise, remove that Promise and race again
            arrCopy.splice(soon.idx, 1);
        }
        // No Promises passed the test
        return false;
    }
    
    // Test harness
    const tests = [
        function allTrue(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => true),
                $timeout(2000).then(() => true),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function twoSecondsTrue(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => false),
                $timeout(2000).then(() => true),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function threeSecondsTrue(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => false),
                $timeout(2000).then(() => false),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function allFalse(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => false),
                $timeout(2000).then(() => false),
                $timeout(3000).then(() => false)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function threeSecondsTrueWithError(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => { throw new Error() }),
                $timeout(2000).then(() => false),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        }
    ]
    
    tests.reduce((acc, curr) => acc.then(()=>curr()), Promise.resolve());

    输出

    // 1 Second true
    2018-07-03T18:41:33.264Z
    true
    2018-07-03T18:41:34.272Z
    
    // 2 Seconds true
    2018-07-03T18:41:34.273Z
    true
    2018-07-03T18:41:36.274Z
    
    // 3 Seconds true
    2018-07-03T18:41:36.274Z
    true
    2018-07-03T18:41:39.277Z
    
    // 3 Seconds false
    2018-07-03T18:41:39.277Z
    false
    2018-07-03T18:41:42.282Z
    
    // 3 Seconds true with error throwing
    2018-07-03T18:41:42.282Z
    true
    2018-07-03T18:41:45.285Z
    

    【讨论】:

    • 你是承诺的忍者
    【解决方案5】:

    根据 str 的回答,我想添加提供回调的功能,这样它就不仅仅适用于 true/false Promises。

    添加回调将允许测试任何 Promise 的结果,并返回成功匹配回调过滤器的 Promise(应返回 true/false 值)。

    Promise.until = function(callback, ...promises) {
      return new Promise(function(resolve, reject) {
        promises.forEach(promise =>
          promise.then(val => callback(val) === true && resolve(val))
        )
      })
    }
    
    // Create some functions that resolve true/false
    function Promise1() {return new Promise(resolve => setTimeout(()=> resolve(false), 1000))}
    function Promise2() {return new Promise(resolve => setTimeout(()=> resolve(true), 3000))}
    function Promise3() {return new Promise(resolve => setTimeout(()=> resolve(false), 1000))}
    
    // Create som functions that resolve objects
    function Promise4() {return new Promise(resolve => setTimeout(()=> resolve({a:1}), 1000))}
    function Promise5() {return new Promise(resolve => setTimeout(()=> resolve({a:2}), 3000))}
    function Promise6() {return new Promise(resolve => setTimeout(()=> resolve({a:123}), 1000))}
    
    // Create some functions that resolve strings
    function Promise7() {return new Promise(resolve => setTimeout(()=> resolve('Brass'), 1000))}
    function Promise8() {return new Promise(resolve => setTimeout(()=> resolve('Monkey'), 500))}
    function Promise9() {return new Promise(resolve => setTimeout(()=> resolve(['Brass', 'Monkey']), 100))}
    
    // Once one resolves `true` we will catch it
    Promise.until(result => result === true, Promise1(), Promise2(), Promise3())
      .then(result => console.log(result));
    
    // Once one resolves where `a` equals 123, we will catch it
    Promise.until(result => result.a === 123, Promise4(), Promise5(), Promise6())
      .then(result => console.log(result));
      
    // Once one resolves where `a` equals 123, we will catch it
    Promise.until(result => result === 'Monkey', Promise7(), Promise8(), Promise9())
      .then(result => console.log(result));

    【讨论】:

      【解决方案6】:

      好的,我将保留已接受的答案。但我根据自己的需要对其进行了一些修改,因为我认为这个更容易阅读和理解

      const firstTrue = Promises => {
          return new Promise((resolve, reject) => {
              // map each promise. if one resolves to true resolve the returned promise immidately with true
              Promises.map(p => {
                  p.then(result => {
                      if(result === true){
                          resolve(true);
                          return;
                      } 
                  });
              });
              // If all promises are resolved and none of it resolved as true, resolve the returned promise with false
              Promise.all(Promises).then(() => {
                  resolve(Promises.indexOf(true) !== -1);
              });   
          });    
      } 
      

      【讨论】:

        猜你喜欢
        • 2017-02-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-08
        • 2016-02-28
        • 2018-05-20
        • 2012-05-16
        • 1970-01-01
        相关资源
        最近更新 更多