使用Promise等待轮询条件满足

Jos*_*iel 43 javascript promise

我需要创建一个JavaScript 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

ensureFooIsSet().then(function(){
    ...
});
Run Code Online (Sandbox Code Playgroud)

我通常会实现最大轮询时间,但不希望这样在这里解决问题.

Den*_*ret 61

一个小的变化是使用命名的IIFE,以便您的代码更简洁,并避免污染外部范围:

function ensureFooIsSet() {
    return new Promise(function (resolve, reject) {
        (function waitForFoo(){
            if (lib.foo) return resolve();
            setTimeout(waitForFoo, 30);
        })();
    });
}
Run Code Online (Sandbox Code Playgroud)

  • @tpae 这不是真的。`setTimeout` 不是递归的,它是异步的。您可以整天运行它,并且不会破坏堆栈,因为堆栈在事件循环运行之前已被清除。 (6认同)
  • 这有递归的危险 (2认同)

Ber*_*rgi 5

有没有更简洁的方法来解决这个问题?

好吧,有了该waitForFoo函数,您根本不需要在构造函数中使用匿名函数:

function ensureFooIsSet() {
    return new Promise(waitForFoo);
}
Run Code Online (Sandbox Code Playgroud)

为避免污染范围,建议将二者包装在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,为避免绑定,需要绕过resolvereject你可以移动它内部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
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样使用它waitFor(lib, "foo", 5000)


alj*_*gom 5

waitFor是我经常使用的一个函数。你传递给它一个函数,它会检查并等待函数返回一个真值,或者直到它超时。

  • 这是一个简单的版本,说明了该功能的作用,但您可能想要使用完整版本,在答案中进一步添加
let sleep = ms => new Promise(r => setTimeout(r, ms));
let waitFor = async function waitFor(f){
    while(!f()) await sleep(1000);
    return f();
};
Run Code Online (Sandbox Code Playgroud)

示例用法:

  • 等待元素存在,然后将其分配给变量
let bed = await waitFor(() => document.getElementById('bedId'))
if(!bed) doSomeErrorHandling();
Run Code Online (Sandbox Code Playgroud)
  • 等待变量为真
await waitFor(() => el.loaded)
Run Code Online (Sandbox Code Playgroud)
  • 等待一些测试为真
await waitFor(() => video.currentTime > 21)
Run Code Online (Sandbox Code Playgroud)
  • 添加特定超时以停止等待
await waitFor(() => video.currentTime > 21, 60*1000)
Run Code Online (Sandbox Code Playgroud)
  • 元素一旦存在就将其作为参数发送
doSomething(await waitFor(() => selector('...'))
Run Code Online (Sandbox Code Playgroud)
  • 通过它一些其他测试功能
if(await waitFor(someTest)) console.log('test passed')
else console.log("test didn't pass after 20 seconds")
Run Code Online (Sandbox Code Playgroud)

完整版本:

这个版本比简单版本处理更多的情况,空,未定义,空数组等,有一个超时,一个频率可以作为参数传递,并在控制台中用一些漂亮的颜色记录它在做什么

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;
}
Run Code Online (Sandbox Code Playgroud)