Use Promise to wait until polled condition is satisfied

后端 未结 5 574
孤独总比滥情好
孤独总比滥情好 2020-12-23 09:34

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 da

相关标签:
5条回答
  • 2020-12-23 09:41

    Is there a more concise approach to this problem?

    Well, with that waitForFoo function you don't need an anonymous function in your constructor at all:

    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 waitForFoo function inside the ensureFooIsSet scope:

    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 resolve and reject you could move it inside the Promise constructor callback like @DenysSéguret suggested.

    Is there a better approach?

    Like @BenjaminGruenbaum commented, you could watch the .foo property to be assigned, e.g. using a 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).

    0 讨论(0)
  • 2020-12-23 09:41

    Here's a waitFor function that I use quite a bit. You pass it a function, and waits until the function returns a truthy value, or until it times out.

    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);
        });
    }
    
    0 讨论(0)
  • 2020-12-23 09:46
    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);
    
    0 讨论(0)
  • 2020-12-23 09:59

    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:

    function ensureFooIsSet() {
        return new Promise(function (resolve, reject) {
            (function waitForFoo(){
                if (lib.foo) return resolve();
                setTimeout(waitForFoo, 30);
            })();
        });
    }
    
    0 讨论(0)
  • 2020-12-23 10:05

    Here's a utility function using async/await and default ES6 promises. The promiseFunction is an async function (or just a function that returns a promise) that returns a truthy value if the requirement is fulfilled (example below).

    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()
    }
    
    0 讨论(0)
提交回复
热议问题