了解 promise.race() 用法

Understanding promise.race() usage

提问人:Martin AJ 提问时间:9/23/2017 最后编辑:KalyanLahkarMartin AJ 更新时间:5/17/2020 访问量:34365

问:

据我所知,关于承诺有两种选择:

好的,我知道有什么作用。它并行运行 promise,如果两者都成功解析,则为您提供值。下面是一个示例:promise.all().then

Promise.all([
  $.ajax({ url: 'test1.php' }),
  $.ajax({ url: 'test2.php' })
])
.then(([res1, res2]) => {
  // Both requests resolved
})
.catch(error => {
  // Something went wrong
});

但我不明白到底应该做什么?换句话说,不使用它有什么区别?假设:promise.race()

$.ajax({
    url: 'test1.php',
    async: true,
    success: function (data) {
        // This request resolved
    }
});

$.ajax({
    url: 'test2.php',
    async: true,
    success: function (data) {
        // This request resolved
    }
});

看?我没有使用过,它的行为就像.无论如何,有没有简单干净的例子可以告诉我什么时候应该使用?promise.race()promise.race()promise.race()

javascript jquery ajax es6-promise

评论

1赞 ggorlen 6/30/2022
还有.Promise.allSettled

答:

47赞 JiangangXiong 9/23/2017 #1

如您所见,将返回首先解析或拒绝的 promise 实例:race()

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, 'one'); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, 'two'); 
});

Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // Both resolve, but p2 is faster
});

对于要使用的场景,也许您想限制请求的成本时间:

var p = Promise.race([
    fetch('/resource-that-may-take-a-while'),
    new Promise(function (resolve, reject) {
         setTimeout(() => reject(new Error('request timeout')), 5000)
    })
])
p.then(response => console.log(response))
p.catch(error => console.log(error))

有了你只需要得到返回的 promise,你就不需要关心 first 返回的 promise 中的哪一个,race()race([])

但是,如果没有 ,就像您的示例一样,您需要关心哪个会首先返回,并在两个回调中调用回调。racesuccess

评论

13赞 Iiridayn 2/13/2018
但是,方法调用将继续,但每当最终返回时,输出将被丢弃。超时应改为取消挂起的请求。fetch
0赞 Dominic 6/11/2019
与 ?Promise.any
4赞 Spoderman4 2/24/2021
@Dominic区别在于它会更快,原因如下:一旦你给它的任何承诺都解决了,无论它们是实现还是拒绝。 一旦您提供的任何承诺都得到履行或全部被拒绝,就会结算,在这种情况下,它将被拒绝并出现 AggregateError。因此,在“全部拒绝”的情况下,速度会更慢,因为它会等到所有承诺都被拒绝。没有太大区别,但仍然如此。Promise.racePromise.racePromise.anyPromise.any
8赞 DannyM 9/23/2017 #2

这里有一个简单的例子来理解 的用法:promise.race()

想象一下,您需要从服务器获取一些数据,如果数据加载时间过长(例如 15 秒),则要显示错误。

你可以用两个 promise 调用 promise.race(),第一个是你的 ajax 请求,第二个是一个简单的setTimeout(() => resolve("ERROR"), 15000)

评论

4赞 Jaromanda X 9/23/2017
为什么不在网络通话中使用超时设置?
0赞 Martin AJ 9/23/2017
@JaromandaX是对的。在您的示例中,在 ajax 请求中使用会更好。无论如何谢谢你,点赞timeout
14赞 Iiridayn 2/13/2018 #3

这是构建超时系统的一部分,其中:

  1. 请求/计算可能会被其他通道取消
  2. 它以后仍然会使用,但我们现在需要交互。

对于第二个示例,可以“立即”显示微调器,同时如果输入速度足够快,则仍默认显示真实内容。尝试运行以下几次 - 请注意,至少有一些控制台消息“立即”出现。这通常是为了在 UI 上执行操作而附加的。

需要注意的关键是 - 结果远不如副作用重要(尽管这是一种代码气味)。Promise.race

// 300 ms _feels_ "instant", and flickers are bad

function getUserInfo(user) {
  return new Promise((resolve, reject) => {
    // had it at 1500 to be more true-to-life, but 900 is better for testing
    setTimeout(() => resolve("user data!"), Math.floor(900*Math.random()));
  });
}

function showUserInfo(user) {
  return getUserInfo().then(info => {
    console.log("user info:", info);
    return true;
  });
}

function showSpinner() {
  console.log("please wait...")
}

function timeout(delay, result) {
  return new Promise(resolve => {
    setTimeout(() => resolve(result), delay);
  });
}
Promise.race([showUserInfo(), timeout(300)]).then(displayed => {
  if (!displayed) showSpinner();
});

灵感来源于captainkovalsky的评论

第一个例子:

function timeout(delay) {
  let cancel;
  const wait = new Promise(resolve => {
    const timer = setTimeout(() => resolve(false), delay);
    cancel = () => {
      clearTimeout(timer);
      resolve(true);
    };
  });
  wait.cancel = cancel;
  return wait;
}


function doWork() {
  const workFactor = Math.floor(600*Math.random());
  const work = timeout(workFactor);
  
  const result = work.then(canceled => {
    if (canceled)
      console.log('Work canceled');
    else
      console.log('Work done in', workFactor, 'ms');
    return !canceled;
  });
  result.cancel = work.cancel;
  return result;
}

function attemptWork() {
  const work = doWork();
  return Promise.race([work, timeout(300)])
    .then(done => {
      if (!done)
        work.cancel();
      return (done ? 'Work complete!' : 'I gave up');
  });
}

attemptWork().then(console.log);

从中可以看出,当超时首先命中时,超时永远不会执行。为了方便测试,它应该失败/成功大约一半/一半。console.log

评论

1赞 Jay Allen 8/23/2018
很好的例子,@Iiridayn。不过,关于第一个,我很好奇为什么您选择将 your 视为浪费块执行的异常,而不是将其视为临时状态,如 .这样一来,您仍然可以正确处理异常,而不是静默地丢弃它们,从而永久显示您的微调器。timeoutcatchsetSpinner()var delayedSpinner = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms, 'please wait...')); Promise.race([showUserInfo(), delayedSpinner(300)])showUserInfo
0赞 Iiridayn 8/25/2018
@JayAllen我不想在显示微调器之前取消超时或检查共享状态。我已经更新了它以解决布尔值(控制台.log返回,这是错误的),因此您可以在生产代码中使用它,:P修改更少。undefined
21赞 ChrisJ 2/16/2018 #4

我已将它用于请求批处理。我们必须将数以万计的记录批处理成批,以便长时间运行。我们可以并行执行此操作,但不希望待处理请求的数量失控。

Race 允许我们保持固定数量的并行承诺运行,并在完成时添加一个替换

const _ = require('lodash')

async function batchRequests(options) {
    let query = { offset: 0, limit: options.limit };

    do {
        batch = await model.findAll(query);
        query.offset += options.limit;

        if (batch.length) {
            const promise = doLongRequestForBatch(batch).then(() => {
                // Once complete, pop this promise from our array
                // so that we know we can add another batch in its place
                _.remove(promises, p => p === promise);
            });
            promises.push(promise);

            // Once we hit our concurrency limit, wait for at least one promise to
            // resolve before continuing to batch off requests
            if (promises.length >= options.concurrentBatches) {
                await Promise.race(promises);
            }
        }
    } while (batch.length);

    // Wait for remaining batches to finish
    return Promise.all(promises);
}

batchRequests({ limit: 100, concurrentBatches: 5 });

评论

1赞 Jim Beveridge 5/16/2020
谢谢。这是我找到的关于如何管理 Promise 值数组的唯一示例。对于尝试使用上述代码的任何其他人,该语句似乎使用了 Lodash 库。这是我使用的代码:_.remove()for (let i = 0; i < promises.length; i++) { if (promises[i] === promise) { promises.splice(i, 1); break; } }
1赞 ChrisJ 5/17/2020
谢谢。我在上面的示例中添加了一个要求,因此 lodash 的使用很清楚
7赞 Willem van der Veen 8/14/2018 #5

总结:

Promise.race是一个 JS 内置函数,它接受 Promise 的可迭代对象(例如 )作为参数。然后,一旦在可迭代对象中传递的 Promise 中的一个被解析或拒绝,此函数就会异步返回 Promise。Array

示例 1:

var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Promise-one'), 500);
});

var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Promise-two'), 100);
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
  // Both resolve, but promise2 is faster than promise 1
});

在此示例中,首先传入一个 Promise 数组。两个 promise 都解析,但 promise1 解析得更快。因此,promise 使用 promise1 的值进行解析,该值是字符串 。Promise.race'Promise-one'

示例 2:

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('succes'), 2000);
});

const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => reject('err'), 1000);
});

Promise.race([promise1, promise2])
  .then((value) => {
  console.log(value);
}).catch((value) => {
  console.log('error: ' + value);
});

在第二个示例中,第二个 promise 的拒绝速度比第一个 promise 的解析速度快。因此,将返回一个被拒绝的 promise,其值是 Promise2 拒绝的值。Promise.race'err'

要理解的关键点是,获取 Promise 的可迭代对象,并根据该可迭代对象中的第一个已解决或拒绝的 promise(具有相应的 or 值)返回一个 Promise。Promice.raceresolve()reject()

评论

0赞 Jay Allen 8/23/2018
对第二句话的一个更正是:“此函数立即返回一个异步计算的 Promise,一旦堆栈为空,就会在可迭代对象中生成第一个 Promise 的值,以解析或拒绝”。它肯定会并行(异步)评估可迭代对象中的所有 Promise,并且在可迭代对象的堆栈为空之前不会解析/拒绝为值。
1赞 acesmndr 10/26/2018
setTimeout(() => resolve('Promise-two'), 1000);这里的超时应该是 100 而不是 1000,以便承诺 2 更快地解决。在那里让我困惑了一会儿
3赞 rab 4/22/2019 #6

让我们采取如下的解决方法示例。Promise.race

const race = (promises) => {
    return new Promise((resolve, reject) => {
        return promises.forEach(f => f.then(resolve).catch(reject));
    })
};

你可以看到函数执行了所有的承诺,但谁先完成,谁就会用包装器解决/拒绝。racePromise