web-dev-qa-db-ger.com

Richtige Schreibweise von Schleifen für Versprechen.

Wie kann eine Schleife korrekt erstellt werden, um sicherzustellen, dass der folgende promise-Aufruf und der verkettete logger.log (res) synchron durch die Iteration laufen? (bluebird)

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

Ich habe den folgenden Weg ausprobiert (Methode aus 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');
}); 

Es scheint zwar zu funktionieren, aber ich glaube nicht, dass es die Reihenfolge des Aufrufs von logger.log (res) garantiert. 

Irgendwelche Vorschläge?

107
user2127480

Ich glaube nicht, dass es die Reihenfolge des Aufrufs von logger.log (res) garantiert. 

Eigentlich tut es. Diese Anweisung wird vor dem Aufruf von resolve ausgeführt.

Irgendwelche Vorschläge?

Viele. Das wichtigste ist, dass Sie das create-versprechen-manuelles Gegenmuster verwenden - tun Sie es nur

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

Zweitens könnte diese while-Funktion stark vereinfacht werden:

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

Drittens würde ich keine while-Schleife (mit einer Abschlussvariablen) verwenden, sondern eine for-Schleife:

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

Wenn Sie wirklich eine allgemeine promiseWhen()-Funktion für diese und andere Zwecke haben möchten, verwenden Sie Bergis Vereinfachungen. Aufgrund der Art und Weise, wie Versprechen funktionieren, ist das Weiterleiten von Rückrufen auf diese Weise im Allgemeinen unnötig und zwingt Sie, durch komplexe kleine Reifen zu springen. 

Soweit ich weiß, dass Sie es versuchen:

  • asynchron eine Reihe von Benutzerdetails für eine Sammlung von E-Mail-Adressen abzurufen (zumindest ist dies das einzige Szenario, das Sinn macht).
  • indem Sie über Rekursion eine .then()-Kette aufbauen.
  • um die ursprüngliche Reihenfolge beizubehalten, wenn die zurückgegebenen Ergebnisse verarbeitet werden.

So definiert ist das Problem eigentlich das unter "The Collection Kerfuffle" in Promise Anti-pattern beschriebene, das zwei einfache Lösungen bietet:

  • parallele asynchrone Aufrufe mit Array.prototype.map() 
  • serielle asynchrone Aufrufe mit Array.prototype.reduce().

Der parallele Ansatz gibt (unkompliziert) das Problem an, das Sie zu vermeiden versuchen - die Reihenfolge der Antworten ist unsicher. Der serielle Ansatz erstellt die erforderliche .then()-Kette - flach - keine Rekursion. 

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

Rufen Sie wie folgt an:

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

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

Wie Sie sehen, besteht keine Notwendigkeit für die hässliche äußere var count oder die zugehörige condition-Funktion. Die Grenze (von 10 in der Frage) wird ausschließlich durch die Länge des Arrays arrayOfEmailAddys bestimmt.

127
Roamer-1888

So mache ich es mit dem Standard-Promise-Objekt.

// 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

Gegeben

  • asyncFn-Funktion
  • array von Elementen

Erforderlich

  • versprechen Verkettung .then () in Serie (in Reihenfolge)
  • native es6

Lösung

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

Ich würde so etwas machen:

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

auf diese Weise ist dataAll ein geordnetes Array aller zu protokollierenden Elemente. Die Protokolloperation wird ausgeführt, wenn alle Versprechen erfüllt sind.

3
Claudio M

Es gibt einen neuen Weg, dies zu lösen, und zwar mit async/await.

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

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

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

3
tomasgvivo

Die vorgeschlagene Funktion von Bergi ist wirklich Nizza:

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

Trotzdem möchte ich eine kleine Ergänzung machen, was bei Versprechungen sinnvoll ist: 

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

Auf diese Weise kann die While-Schleife in eine Versprechungskette eingebettet werden und wird mit lastValue aufgelöst (auch wenn die Aktion () niemals ausgeführt wird). Siehe Beispiel:

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

Verwenden des Standardversprechungsobjekts und Rückgabe der Ergebnisse durch das Versprechen.

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

Nehmen Sie zuerst das Array der Versprechungen (Promise Array) und lösen Sie anschließend dieses Versprechen-Array mit Promise.all(promisearray) auf.

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
}

Verwenden Sie async und warten Sie ab (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'));
}

}
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

Wie wäre es mit BlueBird ?

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

Hier ist eine andere Methode (ES6 mit std Promise). Verwendet Lodash-/Unterstrich-Austrittskriterien (return === false). Beachten Sie, dass Sie leicht eine exitIf () - Methode in Optionen hinzufügen können, um sie in doOne () auszuführen.

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