Подтвердить что ты не робот

Используйте Promise, чтобы подождать, пока условие опроса не будет удовлетворено

Мне нужно создать обещание JavaScript, которое не будет разрешено до тех пор, пока не будет выполнено определенное условие. Допустим, у меня есть сторонняя библиотека, и мне нужно подождать, пока в этой библиотеке не появится определенное условие данных.

Меня интересует сценарий, в котором нет способа узнать, когда выполняется это условие, кроме простого опроса.

Я могу создать обещание, которое ждет его - и этот код работает, но есть ли лучший или более краткий подход к этой проблеме?

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

Я бы обычно реализовывал максимальное время опроса, но не хотел, чтобы это скрывало проблему здесь.

4b9b3361

Ответ 1

Небольшая вариация заключалась бы в использовании именованного IIFE, чтобы ваш код был немного более кратким и позволял избежать загрязнения внешней области:

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

Ответ 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);
    }
}

В качестве альтернативы, чтобы избежать привязки, необходимой для прохождения 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).

Ответ 3

Здесь служебная функция, использующая 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()
}

Ответ 4

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

Ответ 5

Здесь функция waitFor, которой я пользуюсь совсем немного. Вы передаете ему функцию и запускаете ее, пока функция не вернет истинное значение или пока не истечет время ожидания. Примеры использования:

// wait for an element to exist, then assign it to a variable 
let bed = await waitFor(()=>document.getElementById('_1vd7r9f'))
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")

Это код для него

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