等待 Promise 返回的第一个 true 的干净方式
Posted
技术标签:
【中文标题】等待 Promise 返回的第一个 true 的干净方式【英文标题】:Clean way to wait for first true returned by Promise 【发布时间】: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()
可能会成为您想要的,一旦它到达常青浏览器。
【参考方案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.race
和Promise.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);
);
);
【讨论】:
以上是关于等待 Promise 返回的第一个 true 的干净方式的主要内容,如果未能解决你的问题,请参考以下文章
返回一个promise而不等待nodejs中函数中的依赖promise
TypeScript - 等待 observable/promise 完成,然后返回 observable