Javascript 使用 Promise 等待轮询条件满足
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30505960/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Use Promise to wait until polled condition is satisfied
提问by Joseph Gabriel
I need to create a JavaScript Promise that will not resolve until a specific condition is true. Let's say I have a 3rd party library, and I need to wait until a certain data condition exists within that library.
我需要创建一个在特定条件为真之前不会解析的 JavaScript Promise。假设我有一个 3rd 方库,我需要等到该库中存在某个数据条件。
The scenario I am interested in is one where there is no way to know when this condition is satisfied other than by simply polling.
我感兴趣的场景是,除了简单的轮询之外,无法知道何时满足此条件。
I can create a promise that waits on it - and this code works, but is there a better or more concise approach to this problem?
我可以创建一个等待它的承诺 - 这段代码有效,但是有没有更好或更简洁的方法来解决这个问题?
function ensureFooIsSet() {
return new Promise(function (resolve, reject) {
waitForFoo(resolve);
});
}
function waitForFoo(resolve) {
if (!lib.foo) {
setTimeout(waitForFoo.bind(this, resolve), 30);
} else {
resolve();
}
}
Usage:
用法:
ensureFooIsSet().then(function(){
...
});
I would normally implement a max poll time, but didn't want that to cloud the issue here.
我通常会实施最大轮询时间,但不希望这会影响这里的问题。
回答by Denys Séguret
A small variation would be to use a named IIFE so that your code is a little more concise and avoids polluting the external scope:
一个小的变化是使用命名的 IIFE,这样你的代码会更简洁一点,避免污染外部作用域:
function ensureFooIsSet() {
return new Promise(function (resolve, reject) {
(function waitForFoo(){
if (lib.foo) return resolve();
setTimeout(waitForFoo, 30);
})();
});
}
回答by Bergi
Is there a more concise approach to this problem?
有没有更简洁的方法来解决这个问题?
Well, with that waitForFoofunction you don't need an anonymous function in your constructor at all:
好吧,使用该waitForFoo函数,您的构造函数中根本不需要匿名函数:
function ensureFooIsSet() {
return new Promise(waitForFoo);
}
To avoid polluting the scope, I would recommend to either wrap both in an IIFE or to move the waitForFoofunction inside the ensureFooIsSetscope:
为了避免污染范围,我建议要么将两者都包装在 IIFE 中,要么将waitForFoo函数移动到ensureFooIsSet范围内:
function ensureFooIsSet(timeout) {
var start = Date.now();
return new Promise(waitForFoo);
function waitForFoo(resolve, reject) {
if (window.lib && window.lib.foo)
resolve(window.lib.foo);
else if (timeout && (Date.now() - start) >= timeout)
reject(new Error("timeout"));
else
setTimeout(waitForFoo.bind(this, resolve, reject), 30);
}
}
Alternatively, to avoid the binding that is needed to pass around resolveand rejectyou could move it inside the Promiseconstructor callback like @DenysSéguret suggested.
另外,为避免绑定,需要绕过resolve和reject你可以移动它内部Promise的构造回调像@DenysSéguret建议。
Is there a better approach?
有更好的方法吗?
Like @BenjaminGruenbaum commented, you could watch the .fooproperty to be assigned, e.g. using a setter:
就像@BenjaminGruenbaum 评论的那样,您可以观察.foo要分配的属性,例如使用 setter:
function waitFor(obj, prop, timeout, expected) {
if (!obj) return Promise.reject(new TypeError("waitFor expects an object"));
if (!expected) expected = Boolean;
var value = obj[prop];
if (expected(value)) return Promise.resolve(value);
return new Promise(function(resolve, reject) {
if (timeout)
timeout = setTimeout(function() {
Object.defineProperty(obj, prop, {value: value, writable:true});
reject(new Error("waitFor timed out"));
}, timeout);
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
get: function() { return value; },
set: function(v) {
if (expected(v)) {
if (timeout) cancelTimeout(timeout);
Object.defineProperty(obj, prop, {value: v, writable:true});
resolve(v);
} else {
value = v;
}
}
});
});
// could be shortened a bit using "native" .finally and .timeout Promise methods
}
You can use it like waitFor(lib, "foo", 5000).
你可以像waitFor(lib, "foo", 5000).
回答by Leopold W
Here's a utility function using async/awaitand default ES6 promises. The promiseFunctionis an async function (or just a function that returns a promise) that returns a truthy value if the requirement is fulfilled (example below).
这是一个使用async/await和默认 ES6 承诺的实用函数。这promiseFunction是一个异步函数(或只是一个返回承诺的函数),如果满足要求则返回一个真值(下面的例子)。
const promisePoll = (promiseFunction, { pollIntervalMs = 2000 } = {}) => {
const startPoll = async resolve => {
const startTime = new Date()
const result = await promiseFunction()
if (result) return resolve()
const timeUntilNext = Math.max(pollIntervalMs - (new Date() - startTime), 0)
setTimeout(() => startPoll(resolve), timeUntilNext)
}
return new Promise(startPoll)
}
Example usage:
用法示例:
// async function which returns truthy if done
const checkIfOrderDoneAsync = async (orderID) => {
const order = await axios.get(`/order/${orderID}`)
return order.isDone
}
// can also use a sync function if you return a resolved promise
const checkIfOrderDoneSync = order => {
return Promise.resolve(order.isDone)
}
const doStuff = () => {
await promisePoll(() => checkIfOrderDone(orderID))
// will wait until the poll result is truthy before
// continuing to execute code
somethingElse()
}
回答by aljgom
Here's a waitForfunction that I use quite a bit. You pass it a function, and runs it until the function returns a truthy value, or until it times out.
这waitFor是我经常使用的一个函数。你向它传递一个函数,并运行它直到函数返回一个真值,或者直到它超时。
Example usages:
示例用法:
// wait for an element to exist, then assign it to a variable
let bed = await waitFor(()=>document.getElementById('bedId'))
if(!bed) doSomeErrorHandling();
// wait for a variable to be truthy
await waitFor(()=>el.loaded)
// wait for some test to be true
await waitFor(()=>video.currentTime>21)
// add a specific timeout
await waitFor(()=>video.currentTime>21, 60*1000)
// send an element as an argument once it exists
doSomething(await waitFor(()=>selector('...'))
// pass it some other test function
if(await waitFor(someTest)) console.log('test passed')
else console.log("test didn't pass after 20 seconds")
This is the code for it
这是它的代码
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
/* Waits for test function to return a truthy value
example usage:
// wait for an element to exist, then save it to a variable
var el = await waitFor(()=>$('#el_id'))) // second timeout argument optional, or defaults to 20 seconds
*/
async function waitFor(test, timeout_ms=20*1000){
return new Promise(async(resolve,reject)=>{
if( typeof(timeout_ms) != "number") reject("Timeout argument not a number in waitFor(selector, timeout_ms)");
var freq = 100;
var result
// wait until the result is truthy, or timeout
while( result === undefined || result === false || result === null || result.length === 0 ){ // for non arrays, length is undefined, so != 0
if( timeout_ms % 1000 <freq) console.log('%c'+'waiting for: '+ test,'color:#809fff' );
if( (timeout_ms -= freq) < 0 ){ console.log('%c'+'Timeout : ' + test,'color:#cc2900' );
resolve(false);
return;
}
await sleep(freq);
result = typeof(test) === 'string' ? eval(test) : test(); // run the test and update result variable
}
// return result if test passed
console.log('Passed: ', test);
resolve(result);
});
}
回答by Alex Cusack
function getReportURL(reportID) {
return () => viewReportsStatus(reportID)
.then(res => JSON.parse(res.body).d.url);
}
function pollForUrl(pollFnThatReturnsAPromise, target) {
if (target) return P.resolve(target);
return pollFnThatReturnsAPromise().then(someOrNone => pollForUrl(pollFnThatReturnsAPromise, someOrNone));
}
pollForUrl(getReportURL(id), null);

