给定一个承诺列表,你如何实现一个 "raceToSuccess" 助手?
How do you implement a "raceToSuccess" helper, given a list of promises?
我对 ES6 Promise 中的某些内容感到困惑 API。我可以看到同时提交多个异步作业的明确用例,并且 "resolving" 第一次成功。例如,这将服务于多个等效服务器可用的情况,但有些服务器可能已关闭,而其他服务器负载很重且速度很慢,因此我的目标是从第一个服务器获得成功的响应,而忽略其余的(是的,我知道从服务器的角度来看,这是一种令人讨厌的客户端行为方式,但它对最终用户来说很好 ;)
但是,据我所知,我可以使用 "all" 或 "race" 行为。 "all" 行为似乎要等到所有请求都完成,这意味着我必须等待最慢的请求,即使服务器已经完成(实际上,我可能不得不等待超时,因为对于这种情况来说是一场灾难。)然而,"race" 行为似乎让我第一个完成,如果这恰好是一个失败,那也是一场灾难。
API 中是否有允许 "raceToSuccess" 行为的东西,或者我必须手动构建它。就此而言,我将如何手工构建它?
作为旁注,我在 Java 8 CompletableFuture 中发现了相同的谜题,它似乎与 API 非常相似。那么,我是否遗漏了哲学层面的东西?
你自己写这个很容易。
function raceToSuccess(promises) {
return new Promise(
resolve =>
promises.forEach(
promise =>
promise.then(resolve)
)
);
}
这会启动所有的承诺,当任何承诺成功时,都会用它的值解决新的承诺。失败的承诺将被忽略。随后成功的承诺不会导致任何事情发生,因为新的承诺已经解决。请注意,如果 none 的输入承诺解决,则结果承诺将永远不会解决或拒绝。
这是一个修改后的版本,returns 如果所有输入承诺都拒绝,则拒绝承诺:
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));
}
您可能喜欢也可能不喜欢这种行为。如果 none 的输入承诺实现,则返回的承诺将永远不会实现或拒绝。也有可能永久挂起的承诺不会得到 GC,或者某些引擎最终可能会抱怨它们。
这是一个典型的例子,颠倒你的逻辑可以让它更清晰。在这种情况下,您的 "race" 是希望您的拒绝行为实际上是成功行为。
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)
);
}
老话题,但这是我的条目;它本质上是@loganfsmyth 的解决方案,但进行了一些检查以符合 Promise.all()
:
建立的约定
- 空数组作为输入returns(同步)一个已经解决的承诺
- 数组中的非承诺条目导致第一个这样的条目被用作解析值
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);
});
我用超时扩展了@loganfsmyth 方法,并编写了一个小函数:
- 履行所有承诺,
- 等待承诺成功的时间不超过指定的时间 (options.timeOutMs),
- return第一个成功的。
在下面的代码片段中,您可以对其进行测试:
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));
我正在使用一个基于 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');
}
});
});
});
};
它基于 Rich Harris 的 Promise polyfill race 方法。我只是让循环承诺拒绝有条件:它只拒绝主要承诺,如果所有给定的承诺都失败了,否则它会忽略拒绝并解决第一次成功。
用法:
// 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)
})
我之所以使用这种方法而不是 promise 反转方法,是因为在我看来这更具可读性。
为了以最严格的方式解决这个问题,下面有一个版本解决了第一个成功的承诺,但如果所有给定的承诺都失败了,则不做任何事情:
// ignores any and all rejects
Promise.firstResolve = function (promises) {
return new Promise(function (fulfil) {
promises.forEach(function (promise) {
promise.then(fulfil, () => {});
});
});
};
(用法同上)
编辑:这其实和@user663031的建议是一样的。直到现在我才意识到这一点。
为了解决这个问题,我使用了 Promise.rice
和 Promise.allSettled
。
下一个代码等待 Promise.rice
成功值。但如果没有成功的结果。 return 包含所有错误的数组。
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) [{…}, {…}]
Is there something in the API that permits a "raceToSuccess" kind of behavior
现在有了。第 4 阶段已完成 proposal for Promise.any
:
Promise.any()
takes an iterable of Promise objects and, as soon as one of the promises in the iterable fulfills, returns a single promise that resolves with the value from that 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
});
我对 ES6 Promise 中的某些内容感到困惑 API。我可以看到同时提交多个异步作业的明确用例,并且 "resolving" 第一次成功。例如,这将服务于多个等效服务器可用的情况,但有些服务器可能已关闭,而其他服务器负载很重且速度很慢,因此我的目标是从第一个服务器获得成功的响应,而忽略其余的(是的,我知道从服务器的角度来看,这是一种令人讨厌的客户端行为方式,但它对最终用户来说很好 ;)
但是,据我所知,我可以使用 "all" 或 "race" 行为。 "all" 行为似乎要等到所有请求都完成,这意味着我必须等待最慢的请求,即使服务器已经完成(实际上,我可能不得不等待超时,因为对于这种情况来说是一场灾难。)然而,"race" 行为似乎让我第一个完成,如果这恰好是一个失败,那也是一场灾难。
API 中是否有允许 "raceToSuccess" 行为的东西,或者我必须手动构建它。就此而言,我将如何手工构建它?
作为旁注,我在 Java 8 CompletableFuture 中发现了相同的谜题,它似乎与 API 非常相似。那么,我是否遗漏了哲学层面的东西?
你自己写这个很容易。
function raceToSuccess(promises) {
return new Promise(
resolve =>
promises.forEach(
promise =>
promise.then(resolve)
)
);
}
这会启动所有的承诺,当任何承诺成功时,都会用它的值解决新的承诺。失败的承诺将被忽略。随后成功的承诺不会导致任何事情发生,因为新的承诺已经解决。请注意,如果 none 的输入承诺解决,则结果承诺将永远不会解决或拒绝。
这是一个修改后的版本,returns 如果所有输入承诺都拒绝,则拒绝承诺:
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));
}
您可能喜欢也可能不喜欢这种行为。如果 none 的输入承诺实现,则返回的承诺将永远不会实现或拒绝。也有可能永久挂起的承诺不会得到 GC,或者某些引擎最终可能会抱怨它们。
这是一个典型的例子,颠倒你的逻辑可以让它更清晰。在这种情况下,您的 "race" 是希望您的拒绝行为实际上是成功行为。
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)
);
}
老话题,但这是我的条目;它本质上是@loganfsmyth 的解决方案,但进行了一些检查以符合 Promise.all()
:
- 空数组作为输入returns(同步)一个已经解决的承诺
- 数组中的非承诺条目导致第一个这样的条目被用作解析值
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);
});
我用超时扩展了@loganfsmyth 方法,并编写了一个小函数:
- 履行所有承诺,
- 等待承诺成功的时间不超过指定的时间 (options.timeOutMs),
- return第一个成功的。
在下面的代码片段中,您可以对其进行测试:
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));
我正在使用一个基于 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');
}
});
});
});
};
它基于 Rich Harris 的 Promise polyfill race 方法。我只是让循环承诺拒绝有条件:它只拒绝主要承诺,如果所有给定的承诺都失败了,否则它会忽略拒绝并解决第一次成功。
用法:
// 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)
})
我之所以使用这种方法而不是 promise 反转方法,是因为在我看来这更具可读性。
为了以最严格的方式解决这个问题,下面有一个版本解决了第一个成功的承诺,但如果所有给定的承诺都失败了,则不做任何事情:
// ignores any and all rejects
Promise.firstResolve = function (promises) {
return new Promise(function (fulfil) {
promises.forEach(function (promise) {
promise.then(fulfil, () => {});
});
});
};
(用法同上)
编辑:这其实和@user663031的建议是一样的。直到现在我才意识到这一点。
为了解决这个问题,我使用了 Promise.rice
和 Promise.allSettled
。
下一个代码等待 Promise.rice
成功值。但如果没有成功的结果。 return 包含所有错误的数组。
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) [{…}, {…}]
Is there something in the API that permits a "raceToSuccess" kind of behavior
现在有了。第 4 阶段已完成 proposal for Promise.any
:
Promise.any()
takes an iterable of Promise objects and, as soon as one of the promises in the iterable fulfills, returns a single promise that resolves with the value from that 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
});