Söz için döngü yazmanın doğru yolu.


116

Aşağıdaki promise çağrısının ve zincirleme logger.log (res) yineleme yoluyla eşzamanlı olarak çalıştığından emin olmak için doğru bir döngü nasıl oluşturulur ? (Mavikuş)

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

Aşağıdaki yolu denedim ( http://blog.victorquinn.com/javascript-promise- while-loop yöntemi )

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

Çalışıyor gibi görünse de, logger.log (res) çağırma sırasını garanti ettiğini sanmıyorum ;

Baska öneri?


1
Kod bana iyi görünüyor ( loopişlevle özyineleme, eşzamanlı döngüler yapmanın yoludur). Neden garanti olmadığını düşünüyorsun?
hugomg

db.getUser (email) sırayla çağrılma garantilidir. Ancak, db.getUser () 'ın kendisi bir vaat olduğundan, onu sıralı olarak çağırmak, vaatin asenkron özelliği nedeniyle' e-posta 'için veritabanı sorgularının sırayla çalıştığı anlamına gelmez. Böylece, hangi sorgunun önce bitirildiğine bağlı olarak logger.log (res) çağrılır.
user2127480

1
@ user2127480: Ancak döngünün bir sonraki yinelemesi sıralı olarak ancak söz çözüldükten sonra çağrılıyor, bu whilekod böyle mi çalışıyor?
Bergi

Yanıtlar:


78

Logger.log (res) çağırma sırasını garanti ettiğini sanmıyorum;

Aslında öyle. Bu ifade resolvearamadan önce yürütülür .

Baska öneri?

Çok. En önemlisi, yarat-söz ver-manuel anti-modelini kullanmanızdır - sadece

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

İkincisi, bu whileişlev çok basitleştirilebilir:

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

Üçüncüsü, bir whiledöngü (kapanış değişkenli) değil, bir fordöngü kullanmam:

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

2
Hata. Bunun dışında actionsürer valueonun argüman olarak promiseFor. Öyleyse bu kadar küçük bir düzenleme yapmama izin vermezdim. Teşekkürler, çok yardımcı ve zarif.
Gordon

1
@ Roamer-1888: Terminoloji biraz tuhaf olabilir, ancak bir whiledöngünün fordöngü gövdesine bağlı yineleme değişkeni (sayacı) varken bir döngü bazı genel durumu test eder. Aslında, döngüden çok sabit nokta yinelemesine benzeyen daha işlevsel bir yaklaşım kullandım. Kodlarını tekrar kontrol edin, valueparametre farklı.
Bergi

2
Tamam, şimdi görüyorum. Gibi .bind()yeni obfuscates value, ben okunabilirlik için işlevini longhand tercih düşünüyorum. Ve kalın davrandıysam özür dilerim ama bir arada var olmazsa promiseForve promiseWhileolmazsa, biri diğerini nasıl arar?
Roamer-1888

2
@herve Bunu temelde ihmal ve yerini alabilir return …tarafından return Promise.resolve(…). Bir istisnaya karşı ek korumalara conditionveya actionbir istisnaya ihtiyacınız varsa ( Promise.methodsağladığı gibi ), tüm işlev gövdesini birreturn Promise.resolve().then(() => { … })
Bergi

2
@herve Aslında öyle olmalı Promise.resolve().then(action).…ya Promise.resolve(action()).…da dönüş değerini sarmanıza gerek yokthen
Bergi

134

promiseWhen()Bu ve diğer amaçlar için gerçekten genel bir işlev istiyorsanız , o zaman bunu kesinlikle Bergi'nin basitleştirmelerini kullanarak yapın. Ancak, vaatlerin işleyiş şekli nedeniyle geri aramaları bu şekilde geçirmek genellikle gereksizdir ve sizi karmaşık küçük çemberlerden atlamaya zorlar.

Anladığım kadarıyla deniyorsun:

  • e-posta adreslerinden oluşan bir koleksiyon için bir dizi kullanıcı ayrıntısını eşzamansız olarak almak için (en azından mantıklı olan tek senaryo budur).
  • bunu .then()özyineleme yoluyla bir zincir oluşturarak yapmak .
  • döndürülen sonuçları işlerken orijinal sırayı korumak.

Bu şekilde tanımlanan sorun, aslında iki basit çözüm sunan Promise Anti- pattern'de "The Collection Kerfuffle" başlığı altında tartışılan sorundur :

  • kullanarak paralel eşzamansız çağrılar Array.prototype.map()
  • kullanarak seri asenkron çağrılar Array.prototype.reduce().

Paralel yaklaşım (açık bir şekilde) kaçınmaya çalıştığınız sorunu - yanıtların sırasının belirsiz olduğunu - verecektir. Seri yaklaşım, gerekli .then()zinciri oluşturacaktır - düz - özyineleme yok.

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

Aşağıdaki şekilde arayın:

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

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

Gördüğünüz gibi, çirkin dış değişkene countveya bunun ilişkili conditionişleve gerek yok. Sınır (sorudaki 10'luk) tamamen dizinin uzunluğuna göre belirlenir arrayOfEmailAddys.


16
Bunun seçilmiş cevap olması gerektiğini düşünüyor. zarif ve çok tekrar kullanılabilir bir yaklaşım.
ken

1
Bir avın ebeveyne geri yayılıp yayılmayacağını bilen var mı? Örneğin, db.getUser başarısız olursa, (reddetme) hatası yeniden yayılır mı?
wayofthefuture

@wayofthefuture, hayır. Bu şekilde düşünün ..... tarihi değiştiremezsiniz.
Roamer-1888

4
Cevap için teşekkürler. Kabul edilen cevap bu olmalıdır.
klvs

1
@ Roamer-1888 Hatam, orijinal soruyu yanlış okudum. Ben (kişisel olarak), istekleriniz yerine getirildikçe azaltmanız gereken ilk listenin büyüdüğü bir çözüm arıyordum (bu bir sorguDaha çok DB). Bu durumda, bir jeneratör ile indirgeme kullanma fikrini (1) söz zincirinin koşullu uzantısı ve (2) geri dönen sonuçların tüketiminin oldukça güzel bir ayrımı olarak buldum.
jhp

40

Standart Promise nesnesiyle bunu şu şekilde yaparım.

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

Harika yanıt @youngwerth
Jam Risser

3
bu şekilde parametreler nasıl gönderilir?
Akash khan

4
Chain = chain.then (func) satırında @khan, aşağıdakilerden birini yapabilirsiniz: chain = chain.then(func.bind(null, "...your params here")); veya chain = chain.then(() => func("your params here"));
youngwerth

9

verilmiş

  • asyncFn işlevi
  • öğe dizisi

gereklidir

  • zincirleme söz veriyorum. sonra () dizide (sırayla)
  • yerli es6

Çözüm

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

2
Eğer asyncJavaScript Ayrılmış kelime haline üzeredir burada o fonksiyonu yeniden adlandırmak için netlik ekleyebilir.
hippietrail

Ayrıca, şişman okun parantez içinde bir gövde olmadan işlev görmesi, oradaki ifadenin değerlendirdiği şeyi döndürmez mi? Bu, kodu daha kısa hale getirir. currentKullanılmadığını belirten bir yorum da ekleyebilirim .
hippietrail

2
bu doğru yoldur!
teleme.io


3

Bergi'nin önerdiği işlev gerçekten güzel:

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

Yine de, vaatleri kullanırken mantıklı olan küçük bir ekleme yapmak istiyorum:

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

Bu şekilde while döngüsü bir söz zincirine gömülebilir ve lastValue ile çözülür (ayrıca action () hiçbir zaman çalıştırılmazsa). Örneğe bakın:

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

Bunun gibi bir şey yapardım:

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

bu şekilde dataAll, günlüğe kaydedilecek tüm öğelerin sıralı dizisidir. Ve tüm sözler yerine getirildiğinde günlük işlemi gerçekleştirilecektir.


Promise.all aynı zamanda will call vaatlerini arayacaktır. Yani tamamlanma sırası değişebilir. Soru zincirleme vaatler istiyor. Bu yüzden tamamlanma sırası değiştirilmemelidir.
canbax

Düzenleme 1: Promise.all'ı aramanıza hiç gerek yok. Sözler yerine getirildiği sürece paralel olarak yerine getirilecekler.
canbax

1

Eşzamansız kullanın ve bekleyin (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

BlueBird kullanan buna ne dersiniz ?

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

0

İşte başka bir yöntem (ES6 w / std Promise). Lodash / alt çizgi tipi çıkış kriterlerini kullanır (return === false). DoOne () içinde çalıştırma seçeneklerine kolayca bir exitIf () yöntemi ekleyebileceğinizi unutmayın.

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

Standart vaat nesnesini kullanmak ve söze sahip olmak sonuçları geri getirir.

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

İlk önce söz dizisini (söz dizisini) alın ve sonra bu söz dizisini kullanarak çözün 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
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.