【问题标题】:Use Promise to wait until polled condition is satisfied使用 Promise 等待轮询条件满足
【发布时间】:2015-08-10 22:13:24
【问题描述】:

我需要创建一个 JavaScript Promise,它在特定条件为真之前不会解析。假设我有一个 3rd 方库,我需要等到该库中存在某个数据条件。

我感兴趣的场景是,除了简单的轮询之外,无法知道何时满足此条件。

我可以创建一个等待它的承诺 - 这段代码有效,但有没有更好或更简洁的方法来解决这个问题?

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(){
    ...
});

我通常会设置一个最长轮询时间,但不希望这会影响这里的问题。

【问题讨论】:

  • 这看起来不错。您实际上并不需要使用 waitForFoo 污染外部范围,但它是一个细节并且取决于其余代码。您使用的是原生承诺还是特定的库?
  • 同意 - 这只是整体代码的 sn-p。这将使用原生 Promise。我已经提出过一两次这种模式,并且对是否有更好的方法来构建它感兴趣。
  • 一个更简单的方法是 Object.observe 库对象
  • @BenjaminGruenbaum,非常好。我希望有这样的东西。根据浏览器的要求,我可能无法使用它,但这是一个很好的提示。
  • 好吧,然后在旧浏览器中填充它。

标签: javascript promise


【解决方案1】:

一个小的变化是使用一个命名的 IIFE,这样你的代码会更简洁一些,并且避免污染外部范围:

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

【讨论】:

  • 我想使用 IIFE,但没有考虑命名它。
  • 这有递归的危险
  • @tpae 这不是真的。 setTimeout 不是递归的,它是异步的。你可以运行一整天,它不会破坏堆栈,因为堆栈在事件循环运行之前被清除。
【解决方案2】:

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

好吧,有了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);
    }
}

或者,为了避免传递 resolvereject 所需的绑定,您可以像 @DenysSéguret 建议的那样将其移动到 Promise 构造函数回调中。

有更好的方法吗?

就像@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)一样使用它。

【讨论】:

    【解决方案3】:

    这是我经常使用的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)
    
    • 元素一旦存在就作为参数发送
    doSomething(await waitFor(() => selector('...'))
    
    • 通过其他一些测试函数
    if(await waitFor(someTest)) console.log('test passed')
    else console.log("test didn't pass after 20 seconds")
    

    完整版:

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

    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;
    }
    

    【讨论】:

      【解决方案4】:

      这是一个使用 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)
      }
      

      示例用法:

      // 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()
      }
      

      【讨论】:

        【解决方案5】:
        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);
        

        【讨论】:

          猜你喜欢
          • 2020-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-04-22
          • 1970-01-01
          相关资源
          最近更新 更多