web-dev-qa-db-fra.com

Utilisez Promise pour attendre que la condition interrogée soit satisfaite

Je dois créer une promesse JavaScript qui ne sera pas résolue tant qu'une condition spécifique ne sera pas remplie. Disons que j'ai une bibliothèque tierce partie et que je dois attendre qu'une certaine condition de données existe dans cette bibliothèque.

Le scénario qui m’intéresse est celui dans lequel il n’ya aucun moyen de savoir quand cette condition est satisfaite autrement que par simple sondage.

Je peux créer une promesse qui attend - et ce code fonctionne, mais existe-t-il une approche meilleure ou plus concise de ce problème?

function ensureFooIsSet() {
    return new Promise(function (resolve, reject) {
        waitForFoo(resolve);
    });
}

function waitForFoo(resolve) {
    if (!lib.foo) {
        setTimeout(waitForFoo.bind(this, resolve), 30);
    } else {
        resolve();
    }
}

Usage:

ensureFooIsSet().then(function(){
    ...
});

Je mettrais normalement en place un temps d'interrogation maximum, mais je ne voulais pas que cela nuise le problème ici.

28
Joseph Gabriel

Une petite variante consisterait à utiliser un IIFE nommé afin que votre code soit un peu plus concis et évite de polluer la portée externe:

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

Existe-t-il une approche plus concise de ce problème?

Eh bien, avec cette fonction waitForFoo, vous n'avez pas du tout besoin d'une fonction anonyme dans votre constructeur:

function ensureFooIsSet() {
    return new Promise(waitForFoo);
}

Pour éviter de polluer la portée, je vous recommanderais de la placer dans un IIFE ou de déplacer la fonction waitForFoo à l'intérieur de la portée 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);
    }
}

Sinon, pour éviter la liaison nécessaire à la transmission de resolve et reject, vous pouvez la déplacer à l'intérieur du rappel du constructeur Promise, comme l'a suggéré @ DenysSéguret.

Est-ce qu'il y a une meilleure approche?

Comme @BenjaminGruenbaum commenté, vous pouvez regarder l'affectation de la propriété .foo, par exemple. en utilisant un passeur:

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
}

Vous pouvez l'utiliser comme waitFor(lib, "foo", 5000).

4
Bergi
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
Alex Cusack