我需要创建一个 JavaScript Promise,在特定条件成立之前该 Promise 不会解析。 假设我有一个第三方库,我需要等到该库中存在某个数据条件。
我感兴趣的场景是,除了简单的轮询之外,无法知道何时满足此条件。
我可以创建一个等待它的承诺 - 并且这段代码可以工作,但是有没有更好或更简洁的方法来解决这个问题?
function ensureFooIsSet() {
return new Promise(function (resolve, reject) {
waitForFoo(resolve);
});
}
function waitForFoo(resolve) {
if (!lib.foo) {
setTimeout(waitForFoo.bind(this, resolve), 30);
} else {
resolve();
}
}
用途:
ensureFooIsSet().then(function(){
...
});
我通常会实施最大轮询时间,但不希望这让问题变得模糊。
一个小的变化是使用命名的 IIFE,以便您的代码更加简洁并避免污染外部范围:
function ensureFooIsSet() {
return new Promise(function (resolve, reject) {
(function waitForFoo(){
if (lib.foo) return resolve();
setTimeout(waitForFoo, 30);
})();
});
}
这是我经常使用的
waitFor
函数。你向它传递一个函数,它会检查并等待,直到函数返回真值,或者直到超时。
let sleep = ms => new Promise(r => setTimeout(r, ms));
let waitFor = async function waitFor(f){
while(!f()) await sleep(1000);
return f();
};
用法示例:
let bed = await waitFor(() => document.getElementById('bedId'))
if(!bed) doSomeErrorHandling();
await waitFor(() => el.loaded)
await waitFor(() => video.currentTime > 21)
await waitFor(() => video.currentTime > 21, 60*1000)
if(await waitFor(someTest)) console.log('test passed')
else console.log("test didn't pass after 20 seconds")
完整版:
这个版本比简单版本处理更多的情况,null,未定义,空数组等,有超时,频率可以作为参数传递,并用一些漂亮的颜色记录到控制台它正在做什么
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms));}
/**
* Waits for the test function to return a truthy value
* example usage:
* wait for an element to exist, then save it to a variable
* let el = await waitFor(() => document.querySelector('#el_id')))
* timeout_ms and frequency are optional parameters
*/
async function waitFor(test, timeout_ms = 20 * 1000, frequency = 200) {
if (typeof (test) != "function") throw new Error("test should be a function in waitFor(test, [timeout_ms], [frequency])")
if (typeof (timeout_ms) != "number") throw new Error("timeout argument should be a number in waitFor(test, [timeout_ms], [frequency])");
if (typeof (frequency) != "number") throw new Error("frequency argument should be a number in waitFor(test, [timeout_ms], [frequency])");
let logPassed = () => console.log('Passed: ', test);
let logTimedout = () => console.log('%c' + 'Timeout : ' + test, 'color:#cc2900');
let last = Date.now();
let logWaiting = () => {
if(Date.now() - last > 1000) {
last = Date.now();
console.log('%c' + 'waiting for: ' + test, 'color:#809fff');
}
}
let endTime = Date.now() + timeout_ms;
let isNotTruthy = (val) => val === undefined || val === false || val === null || val.length === 0; // for non arrays, length is undefined, so != 0
let result = test();
while (isNotTruthy(result)) {
if (Date.now() > endTime) {
logTimedout();
return false;
}
logWaiting();
await sleep(frequency);
result = test();
}
logPassed();
return result;
}
这个问题有更简洁的方法吗?
好吧,有了
waitForFoo
函数,你根本不需要在构造函数中使用匿名函数:
function ensureFooIsSet() {
return new Promise(waitForFoo);
}
为了避免污染作用域,我建议将两者都包装在 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);
}
}
或者,为了避免传递
resolve
和 reject
所需的绑定,您可以将其移动到 Promise
构造函数回调内部,如@DenysSéguret 建议的那样。
有更好的方法吗?
就像 @BenjaminGruenbaum 评论的那样,您可以观看要分配的
.foo
属性,例如使用设置器:
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
}
您可以像
waitFor(lib, "foo", 5000)
一样使用它。
这是一个使用
async/await
和默认 ES6 Promise 的实用函数。 promiseFunction
是一个异步函数(或者只是一个返回 Promise 的函数),如果满足要求,它会返回一个真值(下面的示例)。
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)
}
使用示例:
// 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()
}
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);
简单! 等待变量具有 X 值。 在示例中,我们等到 zzz = 99。
var zzz=0;
const promise2 = new Promise((resolve, reject) => {
const loop = () =>{
zzz == 99 ? resolve(zzz) : setTimeout(loop);
}
loop();
});
promise2.then((value) => {
alert( "oki:"+value );
});