Çözümler birbiri ardına (yani sırayla) çözülsün mü?


269

Bir dizi dosyayı seri / sıralı olarak okuyan aşağıdaki kodu göz önünde bulundurun. readFilesyalnızca tüm dosyalar sırayla okunduğunda çözülen bir söz verir.

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => 

    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

   readSequential(0); // Start!

  });
};

Yukarıdaki kod çalışır, ancak şeyler sırayla gerçekleşmesi için özyineleme yapmak zorunda sevmiyorum. Garip readSequentialfonksiyonumu kullanmak zorunda kalmamam için bu kodun yeniden yazılmasının daha basit bir yolu var mı?

Başlangıçta kullanmaya çalıştım Promise.all, ancak bu tüm readFileçağrıların aynı anda gerçekleşmesine neden oldu , bu da istediğim şey değil :

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};

2
Önceki bir eşzamansız işlemin bitmesini beklemek zorunda olan her şey bir geri aramada yapılmalıdır. Vaat kullanmak bunu değiştirmez. Yani özyineye ihtiyacınız var.
Barmar

1
Bilginize, bu yığın çerçeve birikimi olmadığından teknik olarak özyineleme değildir. Bir önceki readFileSequential(), bir sonraki çağrılmadan önce geri döndü (eşzamansız olduğu için, orijinal işlev çağrısı zaten döndükten çok sonra tamamlanır).
jfriend00

1
@ jfriend00 Yığın çerçeve birikimi özyineleme için gerekli değildir - yalnızca kendi kendine başvuru içindir. Bu sadece bir tekniktir.
Benjamin Gruenbaum

3
@BenjaminGruenbaum - Demek istediğim, işlevin bir sonraki yinelemeyi başlatmak için kendisini çağırmasıyla ilgili kesinlikle yanlış bir şey yok. Bunun sıfır dezavantajı var ve aslında, zaman uyumsuz işlemleri sıralamanın etkili bir yoludur. Yani, özyineleme gibi görünen bir şeyden kaçınmak için hiçbir sebep yok. Verimsiz olan bazı sorunlara özyinelemeli çözümler var - bu bunlardan biri değil.
jfriend00

1
Hey, JavaScript odasındaki bir tartışma ve istek üzerine bu yanıtı düzenledim, böylece başkalarını kurallı olarak gösterebiliriz. Kabul etmiyorsanız lütfen bana bildirin, ben de geri yükleyip ayrı bir tane açacağım.
Benjamin Gruenbaum

Yanıtlar:


337

Güncelleme 2017 : Ortam destekliyorsa zaman uyumsuz bir işlev kullanırım:

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

İsterseniz, bir zaman uyumsuz oluşturucu kullanarak (ortamınız destekliyorsa) dosyaları ihtiyacınız olana kadar okumayı erteleyebilirsiniz:

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

Güncelleme: İkinci düşünce - Bunun yerine for döngüsü kullanabilirsiniz:

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

Veya daha az, küçültme ile:

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

Diğer söz kitaplıklarında (when ve Bluebird gibi) bunun için yardımcı yöntemler vardır.

Örneğin, Bluebird şöyle olur:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

Bugün async'i kullanmamanın hiçbir nedeni olmamasına rağmen .


2
@ EmreTapcı, hayır. Bir ok işlevinin "=>" işareti döndürmeyi zaten gerektirir.
Maksimum

TypeScript kullanıyorsanız, "için in" döngü çözümü en iyi olduğunu düşünüyorum. İade özyinelemesini azaltın ilk çağrı dönüş türü Promise <void>, ikincisi Promise <Promise <void>> ve benzeri - bence kullanmadan yazmak imkansız
Artur Tagisow

@ArturTagisow typescript (en azından yeni sürümleri) özyinelemeli türleri vardır ve olmalıdır doğru burada türlerini çözmek. Vaat "özyinelemeyle özümsenir" diye bir Vaat <Vaat <T>> diye bir şey yoktur. Promise.resolve(Promise.resolve(15))ile aynıdır Promise.resolve(15).
Benjamin Gruenbaum


72

Görevleri seri olarak çalıştırmayı tercih ediyorum.

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

Daha fazla görevi olan vakalar ne olacak? 10 mu?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}

8
Ve tam görev sayısını bilmediğiniz durumlar ne olacak?
DAMD

1
Ve sadece çalışma zamanında görev sayısını bildiğiniz zaman ne olacak?
joeytwiddle

10
"Bir dizi söz üzerinde işlem yapmak istemezsiniz. Söz verilen spesifikasyona göre, bir söz oluşturulur oluşturulmaz yürütmeye başlar. Yani gerçekten istediğiniz bir dizi söz fabrikasıdır" Gelişmiş hata # 3'e bakın burada: pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
edelans

5
Hat gürültüsünü azaltmak istiyorsanız, şunu da yazabilirsinizresult = result.then(task);
Daniel Buckmaster

1
@DanielBuckmaster evet, ancak dikkatli olun, çünkü görev () bir değer döndürürse bir sonraki çağrışmaya geçirilir. Görevinizde isteğe bağlı bağımsız değişkenler varsa, bu yan etkilere neden olabilir. Geçerli kod sonuçları yutar ve hiçbir argüman olmadan bir sonraki görevi açıkça çağırır.
JHH

63

Bu soru eski, ancak ES6 ve fonksiyonel JavaScript dünyasında yaşıyoruz, bu yüzden nasıl geliştirebileceğimizi görelim.

Vaatler hemen yerine getirildiğinden, sadece bir dizi vaat yaratamayız, hepsi paralel olarak ateşlenir.

Bunun yerine, bir söz veren bir dizi işlev yaratmamız gerekir. Her fonksiyon daha sonra sırayla yürütülür ve bu da içerideki sözü başlatır.

Bunu birkaç yolla çözebiliriz, ancak en sevdiğim yol kullanmaktır reduce.

Vaatlerle birlikte kullanmak biraz zorlaşıyor reduce, bu yüzden bir astarı aşağıda daha küçük sindirilebilir ısırıklara ayırdım.

Bu işlevin özü, reducebaşlangıç ​​değeri Promise.resolve([])veya boş bir dizi içeren bir söz ile başlayarak kullanmaktır .

Bu vaat daha sonra reduceyönteme aktarılacaktır promise. Bu, her bir sözü ardışık olarak zincirlemenin anahtarıdır. Yürütülecek bir sonraki vaat funcve thenyangınlar olduğunda, sonuçlar birleştirilir ve bu vaat daha sonra bir reducesonraki vaat fonksiyonu ile döngü yürütülür .

Tüm sözler yerine getirildikten sonra, geri verilen söz her bir sözün tüm sonuçlarının bir dizisini içerecektir.

ES6 Örneği (bir astar)

/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

ES6 Örneği (ayrılmış)

// broken down to for easier understanding

const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))

Kullanımı:

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))

// execute them serially
serial(funcs)
    .then(console.log.bind(console))

1
çok iyi, teşekkürler, Array.prototype.concat.bind(result)eksik olduğum kısım, işe yarayan ancak daha az serin olan sonuçlara manuel olarak itmek zorunda kaldı
zavr

Hepimiz modern JS ile ilgili olduğumuza göre console.log.bind(console), son örneğinizdeki ifadenin artık genellikle gereksiz olduğuna inanıyorum . Bugünlerde sadece geçebilirsin console.log. Örneğin. serial(funcs).then(console.log). Mevcut düğümlerde ve Chrome'da test edildi.
Molomby

Bu kafamı sarmak biraz zor oldu ama azalma aslında bunu doğru yapıyor mu? Promise.resolve([]).then((x) => { const data = mockApi('/data/1'); return Promise.resolve(x.concat(data)) }).then((x) => { const data = mockApi('/data/2'); return Promise.resolve(x.concat(data)); });
danecando

@danecando, evet bu doğru görünüyor. Ayrıca Promise.resolve iadesinde bırakabilirsiniz, Promise.reject'i çağırmazsanız, döndürülen tüm değerler otomatik olarak çözülür.
joelnet

@joelnet, danecando'nun yorumuna yanıt olarak, aşağıdaki ifadede azalmanın daha doğru ifade olması gerektiğini düşünüyorum, katılıyor musunuz? Promise.resolve([]).then(x => someApiCall('url1').then(r => x.concat(r))).then(x => someApiCall('url2').then(r => x.concat(r)))ve benzeri
bufferoverflow76

37

Bunu basitçe ES6'da yapmak için:

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}

1
Alt çizgi kullanıyor gibi görünüyor. files.forEachDosyaların bir dizi olup olmadığını basitleştirebilirsiniz .
Gustavo Rodrigues

2
Şey ... bu ES5. ES6 yolu olurdu for (file of files) {...}.
Gustavo Rodrigues

1
Promise.resolve()Gerçek hayatta zaten çözülmüş bir vaat yaratmak için kullanmamanız gerektiğini söylüyorsunuz . Neden olmasın? Promise.resolve()daha temiz görünüyor new Promise(success => success()).
Canac

8
@canac Üzgünüm, sadece kelimelerle ilgili bir şakaydı ("boş vaatler ..."). Promise.resolve();Kodunuzda kesinlikle kullanın .
Shridhar Gupta

1
Güzel bir çözüm, takip etmesi kolay. Ben bir işlev içine benim kapsama, bu yüzden return sequence;koymak yerine sonunda çözmek içinsequence.then(() => { do stuff });
Joe Coyle

25

Standart Node.js sözü için basit kullanım:

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

GÜNCELLEME

items-promise , NPM paketini aynı şeyi yapmaya hazırdır.


6
Bunun daha ayrıntılı olarak açıklandığını görmek isterim.
Tyguy7

Aşağıdaki açıklama ile bu cevabın bir varyasyonunu verdim. Teşekkürler
Sarsaparilla

Bu, Noync / beklemede erişimi olmayan Node 7 öncesi ortamlarda yaptığım şeydir. Güzel ve temiz.
JHH

11

Çok sayıda sıralı görev yürütmek zorunda kaldım ve bu cevapları herhangi bir sıralı görevle ilgilenecek bir işlev oluşturmak için kullandım ...

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

İşlev 2 bağımsız değişken + 1 isteğe bağlı alır. İlk argüman üzerinde çalışacağımız dizidir. İkinci argüman görevin kendisidir, bir söz veren bir işlevdir, bir sonraki görev yalnızca bu söz çözüldüğünde başlatılacaktır. Üçüncü argüman, tüm görevler tamamlandığında çalıştırılacak bir geri çağrıdır. Geri arama yapılmazsa, işlev, oluşturduğu sözü döndürür, böylece sonu işleyebiliriz.

İşte bir kullanım örneği:

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

Umarım birine zaman kazandırır ...


İnanılmaz bir çözüm, neredeyse bir hafta içinde bulduğum en iyisi oldu ... Çok iyi açıklanmış, mantıklı iç isimlere, iyi bir örneğe (daha iyi olabilir) sahip, bunu güvenli bir şekilde arayabilirim tuşuna basın ve geri arama ayarlama seçeneğini içerir. sadece güzel! (Sadece adı beni daha anlamlı kılan bir şeyle değiştirdi) .... Diğerleri için TAVSİYE ... 'object_array ' olarak 'Object.keys ( myObject )' kullanarak bir nesneyi yineleyebilirsiniz
DavidTaubmann

Yorumun için teşekkürler! Ben de bu ismi kullanmıyorum ama burada daha açık / basit yapmak istedim.
Salketer

5

Anlayabildiğim en güzel çözüm bluebirdvaatlerdi. Promise.resolve(files).each(fs.readFileAsync);Hangi vaatlerin sırayla çözüleceğini garanti edebilirsiniz .


1
Daha da iyisi: Promise.each(filtes, fs.readFileAsync). Btw, yapmak zorunda değil misin .bind(fs)?
Bergi

Burada hiç kimse bir dizi ve bir dizi arasındaki farkı anlamıyor gibi görünüyor, ikincisi sınırsız / dinamik boyut anlamına geliyor.
vitaly-t

Javascript'teki Dizilerin C stili dillerde sabit boyutlu dizilerle bir ilgisi olmadığını unutmayın. Onlar sadece sayısal anahtar yönetimi cıvatalı nesnelerdir ve önceden belirlenmiş bir boyuta veya limite sahip değildir ( özellikle kullanıldığında değil new Array(int). Tek yapmanız gereken length, uzunluk tabanlı yineleme sırasında kaç endeksin kullanıldığını etkileyen anahtar / değer çiftini önceden ayarlamaktır . dizinin dizin oluşturma veya dizin
sınırlamaları

4

Bu, yukarıdaki başka bir cevabın hafif bir varyasyonudur. Yerel Vaatleri Kullanma:

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

açıklama

Bu görevleriniz [t1, t2, t3]varsa, yukarıdakine eşittir Promise.resolve().then(t1).then(t2).then(t3). Bu azaltma davranışıdır.

Nasıl kullanılır

İlk önce bir görev listesi oluşturmalısınız! Görev, hiçbir argümanı kabul etmeyen bir fonksiyondur. İşlevinize argümanlar iletmeniz gerekiyorsa, bindbir görev oluşturmak için veya diğer yöntemleri kullanın. Örneğin:

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)

4

Tercih ettiğim çözüm:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

Burada yayınlanan diğerlerinden temelde farklı değil:

  • İşlevi serideki öğelere uygular
  • Bir dizi sonuca çözüm getirir
  • Zaman uyumsuz / beklemez (2017'de destek hala oldukça sınırlıdır)
  • Ok fonksiyonlarını kullanır; iyi ve özlü

Örnek kullanım:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

Makul akım Chrome (v59) ve NodeJS (v8.1.2) üzerinde test edilmiştir.


3

Kullanın Array.prototype.reduceve vaatlerinizi bir fonksiyona sarmayı unutmayın, aksi takdirde zaten çalışıyor olacaklar!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

hoş ve kolay ... performans için aynı tohumu tekrar kullanabilmeniz gerekir.

Azaltma kullanırken boş dizilere veya sadece 1 elemanlı dizilere karşı korunmak önemlidir , bu nedenle bu teknik en iyi seçenektir:

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

ve sonra şöyle deyin:

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});

2

Promise nesnesinde bu basit yöntemi oluşturdum:

Promise nesnesine bir Promise.sequence yöntemi oluşturma ve ekleme

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

Kullanımı:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

Promise nesnesinin bu uzantısıyla ilgili en iyi şey, sözlerin stiliyle tutarlı olmasıdır. Promise.all ve Promise.sequence aynı şekilde çağrılır, ancak farklı semantiğe sahiptir.

Dikkat

Sözlerin sıralı olarak yürütülmesi, sözleri kullanmanın genellikle çok iyi bir yolu değildir. Promise.all kullanmak ve tarayıcının kodu olabildiğince hızlı çalıştırmasına izin vermek genellikle daha iyidir. Bununla birlikte, bunun için gerçek kullanım durumları vardır - örneğin, javascript kullanarak bir mobil uygulama yazarken.


Hayır, karşılaştıramazsın Promise.allve senin Promise.sequence. Biri sözleri tekrarlanabilir, diğeri vaatleri geri getiren bir dizi işlevi alır.
Bergi


Bir yineleyici aldığını bilmiyordum. Yine de yeniden yazmak için yeterince kolay olmalı. Bunun neden vaat kurucu antipattern olduğunu açıklayabilir misiniz?
Gönderinizi

@Bergi Tekrarlayıcıları desteklemek için kodu güncelledim. Hala bunun bir anti-patlayıcı olduğunu görmüyorum. Antipatterns genellikle kodlama hatalarından kaçınmak için kılavuz olarak düşünülmelidir ve bu yönergeleri ihlal eden (kütüphane) işlevleri oluşturmak için tamamen geçerlidir.
frodeborli

Evet, bir kütüphane işlevi olarak düşünürseniz sorun yok, ama yine de bu durumda reduceBenjamin'in cevabındaki bir benzerlik çok daha basit.
Bergi

2

PromiseFactories Listesi olan bu işlevi kullanabilirsiniz:

function executeSequentially(promiseFactories) {
    var result = Promise.resolve();
    promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
    });
    return result;
}

Promise Factory, bir Promise döndüren basit bir işlevdir:

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

Çalışır, çünkü bir söz fabrikası talep edilene kadar söz vermez. O zaman bir işlevle aynı şekilde çalışır - aslında, aynı şey!

Bir dizi vaat üzerinde işlem yapmak istemezsiniz. Promise spesifikasyonuna göre, bir söz oluşturulur oluşturulmaz yürütmeye başlar. Yani gerçekten istediğiniz bir dizi söz fabrikası ...

Promises hakkında daha fazla bilgi edinmek istiyorsanız, bu bağlantıyı kontrol etmelisiniz: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html


2

Cevabım https://stackoverflow.com/a/31070150/7542429 adresine dayanıyor .

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

Bu çözüm sonuçları Promise.all () gibi bir dizi olarak döndürür.

Kullanımı:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});

2

@ Joelnet'in cevabını gerçekten beğendim, ama bana göre, bu kodlama tarzı sindirimi biraz zor, bu yüzden aynı çözümü daha okunabilir bir şekilde nasıl ifade edeceğimi anlamaya çalışırken birkaç gün geçirdim. sadece farklı bir sözdizimi ve bazı yorumlarla.

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
  // For every url we return a new function
  return () => {
    return new Promise((resolve) => {
      // random wait in milliseconds
      const randomWait = parseInt((Math.random() * 1000),10)
      console.log('waiting to resolve in ms', randomWait)
      setTimeout(()=>resolve({randomWait, url}),randomWait)
    })
  }
})


const promiseReduce = (acc, next) => {
  // we wait for the accumulator to resolve it's promise
  return acc.then((accResult) => {
    // and then we return a new promise that will become
    // the new value for the accumulator
    return next().then((nextResult) => {
      // that eventually will resolve to a new array containing
      // the value of the two promises
      return accResult.concat(nextResult)
    })
  })
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])

// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
  .then((result) => {
    // let's display the final value here
    console.log('=== The final result ===')
    console.log(result)
  })

2

Bergi'nin fark ettiği gibi, en iyi ve net çözümün BlueBird.each, aşağıdaki kodu kullanması olduğunu düşünüyorum:

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);

2

İlk olarak, bir vaatin yaratılış zamanında yürütüldüğünü anlamalısınız.
Örneğin, bir kodunuz varsa:

["a","b","c"].map(x => returnsPromise(x))

Bunu şu şekilde değiştirmeniz gerekir:

["a","b","c"].map(x => () => returnsPromise(x))

O zaman sıralı olarak sözleri zincirlemeliyiz:

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

yürütme after(), sözün sadece zamanı geldiğinde oluşturulmasını (ve yürütülmesini) sağlayacaktır.


1

Promise nesnesini genişletmek için aşağıdaki kodu kullanın. Vaatlerin reddini yerine getirir ve bir dizi sonuç döndürür

kod

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

Misal

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);

1

İsterseniz sıralı bir söz vermek için reduce kullanabilirsiniz. Örneğin:

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

her zaman ardışık olarak çalışır.


1

Modern ES'yi kullanma:

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task;

    results.push(result);
  }

  return results;
};

//...

const readFiles = await series(files.map(readFile));

1

Async / Await ile (ES7 desteğine sahipseniz)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

( fordöngü kullanmalısınız , değilforEach zaman uyumsuz / bekliyoruz forEach döngü içinde çalışan sorunları var çünkü)

Async / Await olmadan (Promise kullanarak)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}

2
Her birinin içini beklemeyin.
Marcelo Agimóvel

MarceloAgimóvel @ - Ben değil işe çözüme güncelledik forEach(göre bu )
Gil Epshtain

0

Sorunun başlığına dayanarak, "Sırayla Çöz (ardışık olarak vaat ediyor?)?", OP'nin söz konusu anlaşmaların sıralı çağrılara göre sıralı işlemlerle ardışık işlemesi ile daha fazla ilgilendiğini anlayabiliriz .

Bu cevap sunulmaktadır:

  • yanıtların sıralı olarak ele alınması için sıralı çağrıların gerekli olmadığını göstermek.
  • bu sayfanın ziyaretçilerine uygun alternatif kalıplar göstermek - bir yıl sonra hala ilgileniyorsa OP dahil.
  • OP'nin eşzamanlı olarak arama yapmak istemediğine dair iddialarına rağmen, bu durum gerçekten de olabilir, ancak aynı şekilde, başlıkta belirtildiği gibi yanıtların sıralı olarak ele alınma arzusuna dayanan bir varsayım olabilir.

Eşzamanlı çağrılar gerçekten istenmiyorsa, Benjamin Gruenbaum'un sıralı çağrıları (vb.) Kapsamlı bir şekilde kapsayan cevabına bakın.

Bununla birlikte, eşzamanlı çağrılara ve ardından yanıtların sıralı olarak işlenmesine izin veren kalıplarla ilgileniyorsanız (daha iyi performans için), lütfen okumaya devam edin.

Promise.all(arr.map(fn)).then(fn)(Birçok kez yaptığım gibi) veya bir Promise lib'in süslü şekerini (özellikle Bluebird'in) kullanmanız gerektiğini düşünmek cazip gelebilir, ancak ( bu makaleye verilen krediyle ) bir arr.map(fn).reduce(fn)desen, avantajları ile işi yapacak:

  • herhangi bir sözlü lib ile çalışır - jQuery'nin önceden uyumlu sürümleri bile - sadece .then() kullanılır.
  • tek satır moduyla istediğinizi atlamak veya hatada durmak için esneklik sağlar.

İşte için yazılmıştır Q.

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

Not: yalnızca bu bir parça, Q()Q'ya özgüdür. JQuery için, readFile () öğesinin bir jQuery sözü verdiğinden emin olmanız gerekir. A + libs ile yabancı vaatler özümsenecek.

Burada anahtar, azalma olduğunu sequencedizileri söz, işlenmesini vereadFile onların yaratılması vaat değil.

Ve bunu emdikten sonra, .map()sahnenin gerçekten gerekli olmadığını fark ettiğinizde belki biraz kafa karıştırıcı olabilir ! Tüm iş, paralel çağrılar ve doğru sırayla seri kullanım, reduce()tek başına elde edilebilir , ayrıca aşağıdakilere daha fazla esneklik avantajı sağlar:

  • sadece bir satırı hareket ettirerek paralel zaman uyumsuz çağrılardan seri zaman uyumsuz çağrılara dönüştürün - geliştirme sırasında potansiyel olarak yararlıdır.

İşte Qyine.

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

Bu temel kalıp. Arayana veri (örneğin dosyalar veya bunların bir kısmı dönüştürülmesi) vermek istiyorsanız, hafif bir varyanta ihtiyacınız olacaktır.


OP'nin niyetlerine aykırı soruları cevaplamanın iyi bir fikir olduğunu düşünmüyorum…
Bergi

1
Bu sequence.then(() => filePromise)şey bir antipatterndir - hataları mümkün olan en kısa sürede unhandledRejectionyaymaz (ve onları destekleyen kütüphanelerde oluşturur ). Sen doğrusu kullanmalısınız Q.all([sequence, filePromise])veya $.when(sequence, filePromise). Kuşkusuz, bu davranış hataları yoksaymayı veya atlamayı amaçladığınızda istediğiniz şey olabilir , ancak en azından bunu bir dezavantaj olarak belirtmelisiniz.
Bergi

@Bergi, OP'nin bunun niyetlerine gerçekten aykırı olup olmadığı konusunda karar vermesini umuyorum. Değilse, sanırım cevabı sileceğim, bu arada umarım pozisyonumu haklı çıkardım. İyi geri bildirim sağlamayı yeterince ciddiye aldığınız için teşekkür ederiz. Anti-desen hakkında daha fazla bilgi verebilir misiniz veya bir referans verebilir misiniz? Aynı şey temel kalıbı bulduğum makale için de geçerli mi?
Roamer-1888

1
Evet, kodunun üçüncü sürümü ("hem paralel hem de sıralı") aynı soruna sahiptir. "Antipattern" karmaşık hata işlemeye ihtiyaç duyar ve işleyicileri eşzamansız olarak iliştirmeye eğilimlidir, bu da unhandledRejectionolaylara neden olur . Bluebird'de sequence.return(filePromise), aynı davranışa sahip olan ancak reddetmeleri iyi işleyen kullanarak bu sorunu çözebilirsiniz . Herhangi bir referans bilmiyorum, ben sadece onunla geldim - "(anti) desen" henüz bir isim olduğunu sanmıyorum.
Bergi

1
@Bergi, göremediğim bir şeyi açıkça görebilirsiniz :( Bu yeni anti-modelin bir yerde belgelenmesi gerekip gerekmediğini merak ediyorum?
Roamer-1888

0

Yaklaşımınız kötü değil, ancak iki sorunu var: hataları yutar ve Açık Promise İnşaat Antipattern'i kullanır.

Aynı genel stratejiyi uygularken bu sorunların her ikisini de çözebilir ve kodu daha temiz hale getirebilirsiniz:

var Q = require("q");

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  var readSequential = function(index) {
    if (index < files.length) {
      return readFile(files[index]).then(function() {
        return readSequential(index + 1);
      });
    }
  };

  // using Promise.resolve() here in case files.length is 0
  return Promise.resolve(readSequential(0)); // Start!
};

0

CRUD işlemlerini gerçekleştirirken başkalarının Garantili bir sıralı STRICTLY sıralı çözüm yoluna ihtiyacı varsa, aşağıdaki kodu da temel olarak kullanabilirsiniz.

Her işlevi çağırmadan önce bir Promise açıklayan 'return' eklediğiniz sürece ve bu örneği temel olarak bir sonraki .then () işlev çağrısı, bir öncekinin tamamlanmasından sonra SÜREKLİ olarak başlayacaktır:

getRidOfOlderShoutsPromise = () => {
    return readShoutsPromise('BEFORE')
    .then(() => {
        return deleteOlderShoutsPromise();
    })
    .then(() => {
        return readShoutsPromise('AFTER')
    })
    .catch(err => console.log(err.message));
}

deleteOlderShoutsPromise = () => {
    return new Promise ( (resolve, reject) => {
        console.log("in deleteOlderShouts");
        let d = new Date();
        let TwoMinuteAgo = d - 1000 * 90 ;
        All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
            if (err) reject();
            console.log("DELETED OLDs at "+d);
            resolve();        
        });
    });
}

readShoutsPromise = (tex) => {
    return new Promise( (resolve, reject) => {
        console.log("in readShoutsPromise -"+tex);
        All_Shouts
        .find({})
        .sort([['dateTime', 'ascending']])
        .exec(function (err, data){
            if (err) reject();
            let d = new Date();
            console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
            resolve(data);
        });    
    });
}

0

Vaat dizisi için dizi itme ve pop yöntemi kullanılabilir. Ek verilere ihtiyacınız olduğunda yeni vaatler de iletebilirsiniz. Bu kod, React Infinite loader'da sayfa dizisini yüklemek için kullanacağım.

var promises = [Promise.resolve()];

function methodThatReturnsAPromise(page) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(`Resolve-${page}! ${new Date()} `);
			resolve();
		}, 1000);
	});
}

function pushPromise(page) {
	promises.push(promises.pop().then(function () {
		return methodThatReturnsAPromise(page)
	}));
}

pushPromise(1);
pushPromise(2);
pushPromise(3);


0

Cevapların çoğu TÜM vaatlerin sonuçlarını tek tek içermez, bu nedenle birisinin bu özel davranışı araması durumunda, bu özyineleme kullanarak olası bir çözümdür.

Aşağıdaki tarzı takip eder Promise.all:

  • Geri aramadaki sonuç dizisini döndürür .then().

  • Bazı sözler başarısız olursa, geri .catch()arama anında iade edilir .

const promiseEach = (arrayOfTasks) => {
  let results = []
  return new Promise((resolve, reject) => {
    const resolveNext = (arrayOfTasks) => {
      // If all tasks are already resolved, return the final array of results
      if (arrayOfTasks.length === 0) return resolve(results)

      // Extract first promise and solve it
      const first = arrayOfTasks.shift()

      first().then((res) => {
        results.push(res)
        resolveNext(arrayOfTasks)
      }).catch((err) => {
        reject(err)
      })
    }
    resolveNext(arrayOfTasks)
  })
}

// Lets try it 😎

const promise = (time, shouldThrowError) => new Promise((resolve, reject) => {
  const timeInMs = time * 1000
  setTimeout(()=>{
    console.log(`Waited ${time} secs`)
    if (shouldThrowError) reject(new Error('Promise failed'))
    resolve(time)
  }, timeInMs)
})

const tasks = [() => promise(1), () => promise(2)]

promiseEach(tasks)
  .then((res) => {
    console.log(res) // [1, 2]
  })
  // Oops some promise failed
  .catch((error) => {
    console.log(error)
  })

tasksDizi bildirimi hakkında not :

Bu durumda aşağıdaki gösterimi Promise.allkullanmak gibi kullanmak mümkün değildir :

const tasks = [promise(1), promise(2)]

Ve kullanmalıyız:

const tasks = [() => promise(1), () => promise(2)]

Bunun nedeni, JavaScript'in söz verildikten hemen sonra sözünü yerine getirmeye başlamasıdır. Gibi yöntemler kullanırsak Promise.all, hepsinin durumunun olup olmadığını kontrol eder fulfilledveya rejectedexection'ın kendisini başlatmaz. Kullanılıncaya () => promise()kadar yürütmeyi durdururuz.


0
(function() {
  function sleep(ms) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        return resolve();
      }, ms);
    });
  }

  function serial(arr, index, results) {
    if (index == arr.length) {
      return Promise.resolve(results);
    }
    return new Promise(function(resolve, reject) {
      if (!index) {
        index = 0;
        results = [];
      }
      return arr[index]()
        .then(function(d) {
          return resolve(d);
        })
        .catch(function(err) {
          return reject(err);
        });
    })
      .then(function(result) {
        console.log("here");
        results.push(result);
        return serial(arr, index + 1, results);
      })
      .catch(function(err) {
        throw err;
      });
  }

  const a = [5000, 5000, 5000];

  serial(a.map(x => () => sleep(x)));
})();

Burada önemli olan uyku fonksiyonunu nasıl çağırdığınızdır. Bir dizi vaat yerine kendisi bir vaat döndüren bir dizi işlev iletmeniz gerekir.


-1

Bu, bir vaat dizisinin spex.sequence uygulamasına dayalı olarak dinamik / sonsuz dizileri destekleyerek daha genel bir şekilde nasıl işleneceğini genişletmektir :

var $q = require("q");
var spex = require('spex')($q);

var files = []; // any dynamic source of files;

var readFile = function (file) {
    // returns a promise;
};

function source(index) {
    if (index < files.length) {
        return readFile(files[index]);
    }
}

function dest(index, data) {
    // data = resolved data from readFile;
}

spex.sequence(source, dest)
    .then(function (data) {
        // finished the sequence;
    })
    .catch(function (error) {
        // error;
    });

Sadece bu çözüm herhangi bir boyutta dizilerle çalışmakla kalmaz, aynı zamanda ona veri kısma ve yük dengeleme de ekleyebilirsiniz .

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.