web-dev-qa-db-fra.com

Bonne façon d'écrire des boucles pour des promesses.

Comment construire correctement une boucle pour s'assurer que l'appel suivant promise et le chaîné logger.log (res) fonctionnent de manière synchrone lors de l'itération (oiseau bleu)

db.getUser(email).then(function(res) { logger.log(res); }); // this is a promise

J'ai essayé le moyen suivant (méthode de http://blog.victorquinn.com/javascript-promise-while-loop )

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
    var resolver = Promise.defer();

    var loop = function() {
        if (!condition()) return resolver.resolve();
        return Promise.cast(action())
            .then(loop)
            .catch(resolver.reject);
    };

    process.nextTick(loop);

    return resolver.promise;
});

var count = 0;
promiseWhile(function() {
    return count < 10;
}, function() {
    return new Promise(function(resolve, reject) {
        db.getUser(email)
          .then(function(res) { 
              logger.log(res); 
              count++;
              resolve();
          });
    }); 
}).then(function() {
    console.log('all done');
}); 

Bien que cela semble fonctionner, mais je ne pense pas que cela garantisse l'ordre d'appeler logger.log (res); 

Aucune suggestion?

107
user2127480

Je ne pense pas que cela garantisse l'ordre d'appeler logger.log (res); 

En fait, c'est le cas. Cette instruction est exécutée avant l'appel resolve.

Aucune suggestion?

Beaucoup. Le plus important est votre utilisation de créez-une-promesse-anti-modèle manuellement - faites seulement

promiseWhile(…, function() {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 count++;
             });
})…

Deuxièmement, cette fonction while pourrait être beaucoup simplifiée:

var promiseWhile = Promise.method(function(condition, action) {
    if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

Troisièmement, je n’utiliserais pas une boucle while (avec une variable de fermeture) mais une boucle for:

var promiseFor = Promise.method(function(condition, action, value) {
    if (!condition(value)) return value;
    return action(value).then(promiseFor.bind(null, condition, action));
});

promiseFor(function(count) {
    return count < 10;
}, function(count) {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 return ++count;
             });
}, 0).then(console.log.bind(console, 'all done'));
77
Bergi

Si vous voulez vraiment une fonction promiseWhen() générale pour cela et d'autres objectifs, alors n'hésitez pas, faites-le, en utilisant les simplifications de Bergi. Cependant, en raison de la manière dont les promesses fonctionnent, passer des rappels de cette manière est généralement inutile et vous oblige à franchir des étapes complexes. 

Autant que je sache, vous essayez:

  • extraire de manière asynchrone une série de détails utilisateur pour une collection d'adresses électroniques (du moins, c'est le seul scénario qui ait du sens).
  • pour ce faire, créez une chaîne .then() via récursivité.
  • maintenir la commande d'origine lors du traitement des résultats renvoyés.

Ainsi défini, le problème est en réalité celui qui a été discuté dans "The Collection Kerfuffle" dans Promise Anti-patterns , qui offre deux solutions simples:

  • appels asynchrones parallèles à l'aide de Array.prototype.map() 
  • appels série asynchrones utilisant Array.prototype.reduce().

L’approche parallèle donnera (directement) le problème que vous essayez d’éviter, à savoir que l’ordre des réponses est incertain. L’approche en série construira la chaîne .then() requise - à plat - sans récursivité. 

function fetchUserDetails(arr) {
    return arr.reduce(function(promise, email) {
        return promise.then(function() {
            return db.getUser(email).done(function(res) {
                logger.log(res);
            });
        });
    }, Promise.resolve());
}

Appeler comme suit:

//Compose here, by whatever means, an array of email addresses.
var arrayOfEmailAddys = [...];

fetchUserDetails(arrayOfEmailAddys).then(function() {
    console.log('all done');
});

Comme vous pouvez le constater, il n’est pas nécessaire d’utiliser la très variable var count ou sa fonction condition associée. La limite (de 10 dans la question) est entièrement déterminée par la longueur du tableau arrayOfEmailAddys.

127
Roamer-1888

Voici comment je le fais avec l'objet Promise standard.

// Given async function sayHi
function sayHi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Hi');
      resolve();
    }, 3000);
  });
}

// And an array of async functions to loop through
const asyncArray = [sayHi, sayHi, sayHi];

// We create the start of a promise chain
let chain = Promise.resolve();

// And append each function in the array to the promise chain
for (const func of asyncArray) {
  chain = chain.then(func);
}

// Output:
// Hi
// Hi (After 3 seconds)
// Hi (After 3 more seconds)
36
youngwerth

Donné

  • fonction asyncFn
  • tableau d'objets

Champs obligatoires

  • promettent d'enchaîner .then () en série (dans l'ordre)
  • natif es6

Solution

let asyncFn = (item) => {
  return new Promise((resolve, reject) => {
    setTimeout( () => {console.log(item); resolve(true)}, 1000 )
  })
}

// asyncFn('a')
// .then(()=>{return async('b')})
// .then(()=>{return async('c')})
// .then(()=>{return async('d')})

let a = ['a','b','c','d']

a.reduce((previous, current, index, array) => {
  return previous                                    // initiates the promise chain
  .then(()=>{return asyncFn(array[index])})      //adds .then() promise for each item
}, Promise.resolve())
8
kamran

Je ferais quelque chose comme ça:

var request = []
while(count<10){
   request.Push(db.getUser(email).then(function(res) { return res; }));
   count++
};

Promise.all(request).then((dataAll)=>{
  for (var i = 0; i < dataAll.length; i++) {

      logger.log(dataAll[i]); 
  }  
});

de cette manière, dataAll est un tableau ordonné de tous les éléments à consigner. Et l’opération de journalisation s’effectuera lorsque toutes les promesses seront faites.

3
Claudio M

Il existe un nouveau moyen de résoudre ce problème en utilisant async/wait.

async function myFunction() {
  while(/* my condition */) {
    const res = await db.getUser(email);
    logger.log(res);
  }
}

myFunction().then(() => {
  /* do other stuff */
})

https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Statements/async_functionhttps://ponyfoo.com/articles/understanding-javascript-async attendre

3
tomasgvivo

La fonction suggérée par Bergi est vraiment agréable:

var promiseWhile = Promise.method(function(condition, action) {
      if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

Je veux tout de même faire un petit ajout, ce qui est logique lorsque j'utilise des promesses: 

var promiseWhile = Promise.method(function(condition, action, lastValue) {
  if (!condition()) return lastValue;
  return action().then(promiseWhile.bind(null, condition, action));
});

De cette façon, la boucle while peut être intégrée à une chaîne de promesse et résolue avec lastValue (même si l'action () n'est jamais exécutée). Voir exemple:

var count = 10;
util.promiseWhile(
  function condition() {
    return count > 0;
  },
  function action() {
    return new Promise(function(resolve, reject) {
      count = count - 1;
      resolve(count)
    })
  },
  count)
3
Patrick Wieth

Utiliser l'objet de promesse standard et demander à la promesse de renvoyer les résultats.

function promiseMap (data, f) {
  const reducer = (promise, x) =>
    promise.then(acc => f(x).then(y => acc.Push(y) && acc))
  return data.reduce(reducer, Promise.resolve([]))
}

var emails = []

function getUser(email) {
  return db.getUser(email)
}

promiseMap(emails, getUser).then(emails => {
  console.log(emails)
})
0
Chris Blaser

Prenez d’abord un tableau de promesses (tableau de promesses) puis résolvez-les à l’aide de Promise.all(promisearray).

var arry=['raju','ram','abdul','kruthika'];

var promiseArry=[];
for(var i=0;i<arry.length;i++) {
  promiseArry.Push(dbFechFun(arry[i]));
}

Promise.all(promiseArry)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
     console.log(error);
  });

function dbFetchFun(name) {
  // we need to return a  promise
  return db.find({name:name}); // any db operation we can write hear
}

Utilisez async et wait (es6):

function taskAsync(paramets){
 return new Promise((reslove,reject)=>{
 //your logic after reslove(respoce) or reject(error)
})
}

async function fName(){
let arry=['list of items'];
  for(var i=0;i<arry.length;i++){
   let result=await(taskAsync('parameters'));
}

}
0
function promiseLoop(promiseFunc, paramsGetter, conditionChecker, eachFunc, delay) {
    function callNext() {
        return promiseFunc.apply(null, paramsGetter())
            .then(eachFunc)
    }

    function loop(promise, fn) {
        if (delay) {
            return new Promise(function(resolve) {
                setTimeout(function() {
                    resolve();
                }, delay);
            })
                .then(function() {
                    return promise
                        .then(fn)
                        .then(function(condition) {
                            if (!condition) {
                                return true;
                            }
                            return loop(callNext(), fn)
                        })
                });
        }
        return promise
            .then(fn)
            .then(function(condition) {
                if (!condition) {
                    return true;
                }
                return loop(callNext(), fn)
            })
    }

    return loop(callNext(), conditionChecker);
}


function makeRequest(param) {
    return new Promise(function(resolve, reject) {
        var req = https.request(function(res) {
            var data = '';
            res.on('data', function (chunk) {
                data += chunk;
            });
            res.on('end', function () {
                resolve(data);
            });
        });
        req.on('error', function(e) {
            reject(e);
        });
        req.write(param);
        req.end();
    })
}

function getSomething() {
    var param = 0;

    var limit = 10;

    var results = [];

    function paramGetter() {
        return [param];
    }
    function conditionChecker() {
        return param <= limit;
    }
    function callback(result) {
        results.Push(result);
        param++;
    }

    return promiseLoop(makeRequest, paramGetter, conditionChecker, callback)
        .then(function() {
            return results;
        });
}

getSomething().then(function(res) {
    console.log('results', res);
}).catch(function(err) {
    console.log('some error along the way', err);
});
0
Tengiz

Que diriez-vous de celui-ci en utilisant BlueBird ?

function fetchUserDetails(arr) {
    return Promise.each(arr, function(email) {
        return db.getUser(email).done(function(res) {
            logger.log(res);
        });
    });
}
0
wayofthefuture

Voici une autre méthode (ES6 w/std Promise). Utilise les critères de sortie de type lodash/underscore (return === false). Notez que vous pouvez facilement ajouter une méthode exitIf () dans les options à exécuter dans doOne ().

const whilePromise = (fnReturningPromise,options = {}) => { 
    // loop until fnReturningPromise() === false
    // options.delay - setTimeout ms (set to 0 for 1 tick to make non-blocking)
    return new Promise((resolve,reject) => {
        const doOne = () => {
            fnReturningPromise()
            .then((...args) => {
                if (args.length && args[0] === false) {
                    resolve(...args);
                } else {
                    iterate();
                }
            })
        };
        const iterate = () => {
            if (options.delay !== undefined) {
                setTimeout(doOne,options.delay);
            } else {
                doOne();
            }
        }
        Promise.resolve()
        .then(iterate)
        .catch(reject)
    })
};
0
Gary Skiba