En boucle avec des promesses

Quelle serait la manière idiomatique de faire quelque chose comme une boucle avec des promesses. Alors:

Faites quelque chose si la condition est toujours valable, répétez-le, puis faites autre chose.

dosomething.then(possilblydomoresomethings).then(finish) 

Je l’ai fait de cette façon, je me demandais s’il y avait des façons meilleures / plus idéales?

 var q = require('q'); var index = 1; var useless = function(){ var currentIndex = index; console.log(currentIndex) var deferred = q.defer(); setTimeout(function(){ if(currentIndex > 10) deferred.resolve(false); else deferred.resolve(true); },500); return deferred.promise; } var control = function(cont){ var deferred = q.defer(); if(cont){ index = index + 1; useless().then(control).then(function(){ deferred.resolve(); }); } else deferred.resolve(); return deferred.promise; } var chain = useless().then(control).then(function(){console.log('done')}); 

Sortie: 1 2 3 4 5 6 7 8 9 10 11 terminé

    J’utiliserais un object pour envelopper la valeur. De cette façon, vous pouvez avoir une propriété done pour faire savoir à la boucle que vous avez terminé.

     // fn should return an object like // { // done: false, // value: foo // } function loop(promise, fn) { return promise.then(fn).then(function (wrapper) { return !wrapper.done ? loop(Q(wrapper.value), fn) : wrapper.value; }); } loop(Q.resolve(1), function (i) { console.log(i); return { done: i > 10, value: i++ }; }).done(function () { console.log('done'); }); 

    Voici une fonction réutilisable qui, à mon avis, est assez claire.

     var Q = require("q"); // `condition` is a function that returns a boolean // `body` is a function that returns a promise // returns a promise for the completion of the loop function promiseWhile(condition, body) { var done = Q.defer(); function loop() { // When the result of calling `condition` is no longer true, we are // done. if (!condition()) return done.resolve(); // Use `when`, in case `body` does not return a promise. // When it completes loop again otherwise, if it fails, reject the // done promise Q.when(body(), loop, done.reject); } // Start running the loop in the next tick so that this function is // completely async. It would be unexpected if `body` was called // synchronously the first time. Q.nextTick(loop); // The promise return done.promise; } // Usage var index = 1; promiseWhile(function () { return index < = 11; }, function () { console.log(index); index++; return Q.delay(500); // arbitrary async }).then(function () { console.log("done"); }).done(); 

    C’est le moyen le plus simple que j’ai trouvé pour exprimer le modèle de base: vous définissez une fonction qui appelle la promesse, vérifie son résultat, puis s’appelle à nouveau ou se termine.

     const doSomething = value => new Promise(resolve => setTimeout(() => resolve(value >= 5 ? 'ok': 'no'), 1000)) const loop = value => doSomething(value).then(result => { console.log(value) if (result === 'ok') { console.log('yay') } else { return loop(value + 1) } }) loop(1).then(() => console.log('all done!')) 

    Voir en action sur JSBin

    Si vous utilisiez une promesse qui résout ou rejette, vous définissez then et catch au lieu d’utiliser une clause if.

    Si vous aviez une série de promesses, il vous suffirait de changer de loop pour décaler ou sauter la suivante à chaque fois.


    EDIT: Voici une version qui utilise async/await , car c’est 2018:

     const loop = async value => { let result = null while (result != 'ok') { console.log(value) result = await doSomething(value) value = value + 1 } console.log('yay') } 

    Voir en action sur CodePen

    Comme vous pouvez le voir, il utilise une boucle while normale et aucune récursivité.

    Ceci est pour bluebird et non pour q mais puisque vous n’avez pas mentionné q spécifiquement … dans l’api doc de bluebird, l’auteur mentionne que retourner une fonction générasortingce de promesse serait plus idiomatique que d’utiliser des différés.

     var Promise = require('bluebird'); var i = 0; var counter = Promise.method(function(){ return i++; }) function getAll(max, results){ var results = results || []; return counter().then(function(result){ results.push(result); return (result < max) ? getAll(max, results) : results }) } getAll(10).then(function(data){ console.log(data); }) 

    Comme je ne peux pas commenter la réponse de Stuart K, j’appendai un petit peu ici. Basé sur la réponse de Stuart K, vous pouvez le résumer à un concept étonnamment simple: réutiliser une promesse non réalisée . Ce qu’il a est essentiellement:

    1. Créer une nouvelle instance d’une promesse différée
    2. Définissez votre fonction que vous souhaitez appeler en boucle
    3. Dans cette fonction:
      1. Vérifiez si vous avez terminé. et lorsque vous résolvez la promesse créée au n ° 1 et la renvoyez.
      2. Si vous n’avez pas fini, dites à Q d’utiliser la promesse existante et d’exécuter la fonction non remplie qui est la fonction “récursive”, ou échouer si elle est morte. Q.when (promis, votreFonction, échecFonction)
    4. Après avoir défini votre fonction, utilisez Q pour déclencher la fonction pour la première fois en utilisant Q.nextTick (yourFunction)
    5. Enfin, retournez votre nouvelle promesse à l’appelant (ce qui déclenchera le tout).

    La réponse de Stuart est pour une solution plus générique, mais les bases sont géniales (une fois que vous réalisez comment cela fonctionne).

    Ce modèle est maintenant plus facilement appelé en utilisant q-flow . Un exemple, pour le problème ci-dessus:

     var q = require('q'); require('q-flow'); var index = 1; q.until(function() { return q.delay(500).then(function() { console.log(index++); return index > 10; }); }).done(function() { return console.log('done'); }); 

    Voici une extension du prototype Promise pour imiter le comportement d’une boucle for . Il prend en charge les promesses ou les valeurs immédiates pour les sections d’initialisation, de condition, de boucle et d’incrément. Il prend également en charge toutes les exceptions et ne présente pas de memory leaks. Un exemple est donné ci-dessous sur la façon de l’utiliser.

     var Promise = require('promise'); // Promise.loop([properties: object]): Promise() // // Execute a loop based on promises. Object 'properties' is an optional // argument with the following fields: // // initialization: function(): Promise() | any, optional // // Function executed as part of the initialization of the loop. If // it returns a promise, the loop will not begin to execute until // it is resolved. // // Any exception occurring in this function will finish the loop // with a rejected promise. Similarly, if this function returns a // promise, and this promise is reject, the loop finishes right // away with a rejected promise. // // condition: function(): Promise(result: bool) | bool, optional // // Condition evaluated in the beginning of each iteration of the // loop. The function should return a boolean value, or a promise // object that resolves with a boolean data value. // // Any exception occurring during the evaluation of the condition // will finish the loop with a rejected promise. Similarly, it this // function returns a promise, and this promise is rejected, the // loop finishes right away with a rejected promise. // // If no condition function is provided, an infinite loop is // executed. // // body: function(): Promise() | any, optional // // Function acting as the body of the loop. If it returns a // promise, the loop will not proceed until this promise is // resolved. // // Any exception occurring in this function will finish the loop // with a rejected promise. Similarly, if this function returns a // promise, and this promise is reject, the loop finishes right // away with a rejected promise. // // increment: function(): Promise() | any, optional // // Function executed at the end of each iteration of the loop. If // it returns a promise, the condition of the loop will not be // evaluated again until this promise is resolved. // // Any exception occurring in this function will finish the loop // with a rejected promise. Similarly, if this function returns a // promise, and this promise is reject, the loop finishes right // away with a rejected promise. // Promise.loop = function(properties) { // Default values properties = properties || {}; properties.initialization = properties.initialization || function() { }; properties.condition = properties.condition || function() { return true; }; properties.body = properties.body || function() { }; properties.increment = properties.increment || function() { }; // Start return new Promise(function(resolve, reject) { var runInitialization = function() { Promise.resolve().then(function() { return properties.initialization(); }) .then(function() { process.nextTick(runCondition); }) .catch(function(error) { reject(error); }); } var runCondition = function() { Promise.resolve().then(function() { return properties.condition(); }) .then(function(result) { if (result) process.nextTick(runBody); else resolve(); }) .catch(function(error) { reject(error); }); } var runBody = function() { Promise.resolve().then(function() { return properties.body(); }) .then(function() { process.nextTick(runIncrement); }) .catch(function(error) { reject(error); }); } var runIncrement = function() { Promise.resolve().then(function() { return properties.increment(); }) .then(function() { process.nextTick(runCondition); }) .catch(function(error) { reject(error); }); } // Start running initialization process.nextTick(runInitialization); }); } // Promise.delay(time: double): Promise() // // Returns a promise that resolves after the given delay in seconds. // Promise.delay = function(time) { return new Promise(function(resolve) { setTimeout(resolve, time * 1000); }); } // Example var i; Promise.loop({ initialization: function() { i = 2; }, condition: function() { return i < 6; }, body: function() { // Print "i" console.log(i); // Exception when 5 is reached if (i == 5) throw Error('Value of "i" reached 5'); // Wait 1 second return Promise.delay(1); }, increment: function() { i++; } }) .then(function() { console.log('LOOP FINISHED'); }) .catch(function(error) { console.log('EXPECTED ERROR:', error.message); }); 
     var Q = require('q') var vetor = ['a','b','c'] function imprimeValor(elements,initValue,defer){ console.log( elements[initValue++] ) defer.resolve(initValue) return defer.promise } function Qloop(initValue, elements,defer){ Q.when( imprimeValor(elements, initValue, Q.defer()), function(initValue){ if(initValue===elements.length){ defer.resolve() }else{ defer.resolve( Qloop(initValue,elements, Q.defer()) ) } }, function(err){ defer.reject(err) }) return defer.promise } Qloop(0, vetor,Q.defer()) 

    J’utilise maintenant ceci:

     function each(arr, work) { function loop(arr, i) { return new Promise(function(resolve, reject) { if (i >= arr.length) {resolve();} else try { Promise.resolve(work(arr[i], i)).then(function() { resolve(loop(arr, i+1)) }).catch(reject); } catch(e) {reject(e);} }); } return loop(arr, 0); } 

    Cela accepte un tableau arr et une fonction work et renvoie une Promise . La fonction fournie est appelée une fois pour chaque élément du tableau et reçoit l’élément en cours et son index dans le tableau. Il peut être synchrone ou asynchrone, auquel cas il doit renvoyer une promesse.

    Vous pouvez l’utiliser comme ceci:

     var items = ['Hello', 'cool', 'world']; each(items, function(item, idx) { // this could simply be sync, but can also be async // in which case it must return a Promise return new Promise(function(resolve){ // use setTimeout to make this async setTimeout(function(){ console.info(item, idx); resolve(); }, 1000); }); }) .then(function(){ console.info('DONE'); }) .catch(function(error){ console.error('Failed', error); }) 

    Chaque élément du tableau sera traité à son tour. Une fois que tout est géré, le code donné à .then() sera exécuté ou, si une erreur survient, le code donné à .catch() . Dans la fonction de work , vous pouvez throw une Error (dans le cas de fonctions synchrones) ou reject la Promise (dans le cas de fonctions asynchrones) pour abandonner la boucle.

     function each(arr, work) { function loop(arr, i) { return new Promise(function(resolve, reject) { if (i >= arr.length) {resolve();} else try { Promise.resolve(work(arr[i], i)).then(function() { resolve(loop(arr, i+1)) }).catch(reject); } catch(e) {reject(e);} }); } return loop(arr, 0); } var items = ['Hello', 'cool', 'world']; each(items, function(item, idx) { // this could simply be sync, but can also be async // in which case it must return a Promise return new Promise(function(resolve){ // use setTimeout to make this async setTimeout(function(){ console.info(item, idx); resolve(); }, 1000); }); }) .then(function(){ console.info('DONE'); }) .catch(function(error){ console.error('Failed', error); }) 

    En utilisant la promesse de l’ES6, j’ai trouvé ça. Il enchaîne les promesses et rend une promesse. Ce n’est pas techniquement une boucle while, mais montre comment itérer de manière synchrone les promesses.

     function chain_promises(list, fun) { return list.reduce( function (promise, element) { return promise.then(function () { // I only needed to kick off some side-effects. If you need to get // a list back, you would append to it here. Or maybe use // Array.map instead of Array.reduce. fun(element); }); }, // An initial promise just starts things off. Promise.resolve(true) ); } // To test it... function test_function (element) { return new Promise(function (pass, _fail) { console.log('Processing ' + element); pass(true); }); } chain_promises([1, 2, 3, 4, 5], test_function).then(function () { console.log('Done.'); }); 

    Je pensais que je pourrais aussi bien jeter mon chapeau sur le ring, en utilisant les promesses ES6 …

     function until_success(executor){ var before_retry = undefined; var outer_executor = function(succeed, reject){ var rejection_handler = function(err){ if(before_retry){ try { var pre_retry_result = before_retry(err); if(pre_retry_result) return succeed(pre_retry_result); } catch (pre_retry_error){ return reject(pre_retry_error); } } return new Promise(executor).then(succeed, rejection_handler); } return new Promise(executor).then(succeed, rejection_handler); } var outer_promise = new Promise(outer_executor); outer_promise.before_retry = function(func){ before_retry = func; return outer_promise; } return outer_promise; } 

    L’argument de l’ executor est identique à celui transmis à un constructeur Promise , mais sera appelé à plusieurs resockets jusqu’à ce qu’il déclenche le rappel réussi. La fonction before_retry permet de gérer les erreurs sur les tentatives infructueuses. Si elle renvoie une valeur véridique, elle sera considérée comme une forme de réussite et la “boucle” se terminera, avec cette vérité comme résultat. Si aucune fonction before_retry n’est enregistrée ou si elle renvoie une valeur falsey, la boucle s’exécutera pour une autre itération. La troisième option est que la fonction before_retry génère une erreur elle-même. Si cela se produit, alors la “boucle” se terminera, transmettant cette erreur comme une erreur.


    Voici un exemple:

     var counter = 0; function task(succ, reject){ setTimeout(function(){ if(++counter < 5) reject(counter + " is too small!!"); else succ(counter + " is just right"); }, 500); // simulated async task } until_success(task) .before_retry(function(err){ console.log("failed attempt: " + err); // Option 0: return falsey value and move on to next attempt // return // Option 1: uncomment to get early success.. //if(err === "3 is too small!!") // return "3 is sort of ok"; // Option 2: uncomment to get complete failure.. //if(err === "3 is too small!!") // throw "3rd time, very unlucky"; }).then(function(val){ console.log("finally, success: " + val); }).catch(function(err){ console.log("it didn't end well: " + err); }) 

    Sortie pour l'option 0:

     failed attempt: 1 is too small!! failed attempt: 2 is too small!! failed attempt: 3 is too small!! failed attempt: 4 is too small!! finally, success: 5 is just right 

    Sortie pour l'option 1:

     failed attempt: 1 is too small!! failed attempt: 2 is too small!! failed attempt: 3 is too small!! finally, success: 3 is sort of ok 

    Sortie pour l'option 2:

     failed attempt: 1 is too small!! failed attempt: 2 is too small!! failed attempt: 3 is too small!! it didn't end well: 3rd time, very unlucky 

    J’ai écrit un module qui vous aide à faire des boucles chaînées de tâches asynchrones avec des promesses, il est basé sur la réponse ci-dessus fournie par juandopazo

     /** * Should loop over a task function which returns a "wrapper" object * until wrapper.done is true. A seed value wrapper.seed is propagated to the * next run of the loop. * * todo/maybe? Reject if wrapper is not an object with done and seed keys. * * @param {Promise|*} seed * @param {Function} taskFn * * @returns {Promise.< *>} */ function seedLoop(seed, taskFn) { const seedPromise = Promise.resolve(seed); return seedPromise .then(taskFn) .then((wrapper) => { if (wrapper.done) { return wrapper.seed; } return seedLoop(wrapper.seed, taskFn); }); } // A super simple example of counting to ten, which doesn't even // do anything asynchronous, but if it did, it should resolve to // a promise that returns the { done, seed } wrapper object for the // next call of the countToTen task function. function countToTen(count) { const done = count > 10; const seed = done ? count : count + 1; return {done, seed}; } seedLoop(1, countToTen).then((result) => { console.log(result); // 11, the first value which was over 10. }); 

    https://github.com/CascadeEnergy/promise-seedloop