ForEach döngüsüyle async / await kullanma


1128

Bir döngü içinde async/ kullanmayla ilgili herhangi bir sorun var mı? Ben bir dizi dosya ve her dosyanın içeriği üzerinde döngü çalışıyorum .awaitforEachawait

import fs from 'fs-promise'

async function printFiles () {
  const files = await getFilePaths() // Assume this works fine

  files.forEach(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
}

printFiles()

Bu kod işe yarıyor, ancak bununla ilgili bir sorun olabilir mi? Birisi bana böyle async/ awaitdaha yüksek bir işlev kullanmanız gerektiğini söylemişti , bu yüzden bununla ilgili herhangi bir sorun olup olmadığını sormak istedim.

Yanıtlar:


2147

Eminim kod çalışır, ama eminim yapmak için beklediğiniz ne yapmaz. Birden fazla eşzamansız çağrıyı tetikler, ancak printFilesişlev hemen bundan sonra geri döner.

Sırayla okuma

Dosyaları sırayla okumak istiyorsanız,forEach gerçekten kullanamazsınız . Bunun for … ofyerine, awaitbeklendiği gibi çalışacak modern bir döngü kullanın :

async function printFiles () {
  const files = await getFilePaths();

  for (const file of files) {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }
}

Paralel okuma

Dosyaları paralel olarak okumak istiyorsanız,forEach gerçekten kullanamazsınız . Her asyncgeri çağırma işlevi çağrıları bir söz çıkmıyor, ama onları bekleyen yerine onları uzak harcıyoruz. Bunun mapyerine sadece kullanın ve alacağınız vaat dizisini bekleyebilirsiniz Promise.all:

async function printFiles () {
  const files = await getFilePaths();

  await Promise.all(files.map(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  }));
}

33
Neden for ... of ...işe yaradığını açıklayabilir misiniz ?
Demonbane

84
tamam nedenini biliyorum ... Babel kullanmak async/ awaitişlevini dönüştürecek ve forEachher yinelemenin diğerleriyle hiçbir ilgisi olmayan ayrı bir jeneratör işlevi olduğu anlamına gelir. bu yüzden bağımsız olarak icra edilirler ve next()başkalarıyla bağlamları yoktur . Aslında, basit bir for()döngü de çalışır çünkü iterasyonlar da tek bir jeneratör fonksiyonundadır.
Demonbane

21
@Demonbane: Kısacası, çalışmak üzere tasarlandığından :-) tüm kontrol yapıları da dahil olmak üzere awaitmevcut işlev değerlendirmesini askıya alır . Evet, bu bakımdan jeneratörlere oldukça benzer (bu yüzden asenkron / beklemek için kullanılırlar).
Bergi

3
@ arve0 Aslında, bir asyncişlev bir Promiseyürütücünün geri aramasından oldukça farklıdır , ancak evet mapgeri arama her iki durumda da bir söz verir.
Bergi

5
JS vaatleri hakkında bilgi edinmek için geldiğinizde, bunun yerine yarım saat çeviri Latince kullanın. Umarım gurur duyursun @Bergi;)
Félix Gagnon-Grenier

188

ES2018 ile, yukarıdaki yanıtların tümünü büyük ölçüde basitleştirebilirsiniz:

async function printFiles () {
  const files = await getFilePaths()

  for await (const file of fs.readFile(file, 'utf8')) {
    console.log(contents)
  }
}

Spesifikasyona bakın: teklif-zaman uyumsuz-yineleme


2018-09-10: Bu yanıt son zamanlarda çok dikkat çekiyor, eşzamansız yineleme hakkında daha fazla bilgi için lütfen Axel Rauschmayer'ın blog gönderisine bakın: ES2018: eşzamansız yineleme


4
Eğer asenkron yineleme hakkında daha fazla bilgi edinmek isteyen herkes için cevabınızdaki spesifikasyona bir bağlantı koyabilirsiniz .
saadq

8
Yineleyicideki dosya yerine içerik olmamalı
FluffyBeing 18:18

10
İnsanlar neden bu cevabı destekliyor? Yanıt, soru ve teklife daha yakından bakın. Sonra ofbir dizi döndürecek zaman uyumsuz işlevi olmalıdır. Çalışmıyor ve Francisco dedi;
Yevhenii Herasymchuk

3
@AntonioVal ile tamamen aynı fikirde. Bu bir cevap değil.
Yevhenii Herasymchuk

2
Bunun bir cevap olmadığını kabul etsem de, bir teklifi onaylamak, popülerliğini artırmanın, daha sonra kullanmak üzere daha önce kullanılabilir hale getirmenin bir yoludur.
Robert Molina

61

(S'nin Promise.allçözülme Array.prototype.mapsırasını garanti etmeyen) ile birlikte yerine, bir çözümle başlayarak Promisekullanıyorum :Array.prototype.reducePromise

async function printFiles () {
  const files = await getFilePaths();

  await files.reduce(async (promise, file) => {
    // This line will wait for the last async function to finish.
    // The first iteration uses an already resolved Promise
    // so, it will immediately continue.
    await promise;
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }, Promise.resolve());
}

1
Bu mükemmel çalışıyor, çok teşekkür ederim. Eğer burada neler olduğunu açıklayabilir misiniz Promise.resolve()ve await promise;?
parrker9

1
Bu çok havalı. Dosyaların bir kerede değil, sırayla okunacağını düşünmekte haklı mıyım?
GollyJer

1
@ parrker9 Promise.resolve()zaten çözülmüş döndüren Promiseböylece nesne reducebir sahip Promiseile başlamak. zincirde sonuncunun çözülmesini await promise;bekleyecek Promise. @GollyJer Dosyalar sırayla birer birer işlenir.
Timothy Zorn

Çok serin azaltmak kullanımı, yorum için teşekkürler! Sadece yorumlarda bahsedilen diğer yöntemlerin aksine, bu eşzamanlı olduğunu, yani dosyaların birbiri ardına okunduğunu ve paralel olmadığını okudum (azaltma işlevinin bir sonraki yinelemesi bir öncekine bağlı olduğundan) yineleme, senkron olmalıdır).
Shay Yzhakov

1
@Shay, Yani sıralı değil, eşzamanlı değil. Bu hala eşzamansız - başka şeyler zamanlanmışsa, buradaki yinelemeler arasında çalışırlar.
Timothy Zorn

32

P-yineleme onlar zaman uyumsuz / bekliyoruz ile çok basit bir şekilde kullanılabilmesi için npm aletlerin Dizi iterasyon yöntemleri üzerinde modülü.

Vakanızla ilgili bir örnek:

const { forEach } = require('p-iteration');
const fs = require('fs-promise');

(async function printFiles () {
  const files = await getFilePaths();

  await forEach(files, async (file) => {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  });
})();

1
JS kendisi ile aynı işlevleri / yöntemleri olduğu gibi bunu seviyorum - benim durumumda someyerine yerine forEach. Teşekkürler!
mikemaccana

25

İşte bazı forEachAsyncprototipler. Bunlara ihtiyacınız olacağını unutmayın await:

Array.prototype.forEachAsync = async function (fn) {
    for (let t of this) { await fn(t) }
}

Array.prototype.forEachAsyncParallel = async function (fn) {
    await Promise.all(this.map(fn));
}

Not kendi kodunda bu içerebilir iken, bunu yapabileceğinden (onların globalsi kirletici kaçınmak için) başkalarına dağıtmak kütüphanelerde içermemelidir.


1
Her şeyi doğrudan prototipe eklemekte tereddüt
etsem de

2
Gelecekte isim benzersiz olduğu sürece (kullanacağım gibi _forEachAsync) bu makul. Ayrıca, çok sayıda kaynak kodu kaydettiği için en güzel cevap olduğunu düşünüyorum.
mikemaccana

1
@estus Başkalarının kodlarını kirletmekten kaçınmak içindir. Kod kişisel kuruluşumuza aitse ve globaller iyi tanımlanmış bir dosyadaysa ( globals.jsiyi olurdu) globalleri istediğimiz gibi ekleyebiliriz.
mikemaccana

1
@mikemaccana Genel kabul görmüş kötü uygulamalardan kaçınmak içindir. Bu doğru, yalnızca nadiren gerçekleşen birinci taraf kodu kullandığınız sürece yapılabilir. Sorun şu ki, üçüncü taraf kütüphanelerini kullandığınızda, aynı şekilde hisseden ve aynı küreselleri değiştiren başka bir adam olabilir, çünkü bir lib yazıldığı sırada iyi bir fikir gibi görünüyordu.
Estus Flask

1
@estus Tabii. Buradaki (özellikle üretken olmayan) tartışmayı kaydetmek için soruya bir uyarı ekledim.
mikemaccana

6

@ Bergi'nin cevabına ek olarak , üçüncü bir alternatif daha sunmak istiyorum. @ Bergi'nin 2. örneğine çok benziyor, ancak her birini readFileayrı ayrı beklemek yerine , her biri sonunda beklediğiniz bir dizi söz yaratıyorsunuz.

import fs from 'fs-promise';
async function printFiles () {
  const files = await getFilePaths();

  const promises = files.map((file) => fs.readFile(file, 'utf8'))

  const contents = await Promise.all(promises)

  contents.forEach(console.log);
}

Yine de bir Promise nesnesi döndürdüğünden , iletilen işlevin .map()olması gerekmez . Bu nedenle , gönderilebilen bir Promise nesneleri dizisidir .asyncfs.readFilepromisesPromise.all()

@ Bergi'nin cevabında, konsol dosya içeriğini okundukları sıraya göre kaydedebilir. Örneğin, gerçekten küçük bir dosya gerçekten büyük bir dosyadan önce okumayı bitirirse, küçük dosya dizideki büyük dosyadan sonra gelse bile ilk olarak günlüğe kaydedilir files. Ancak, yukarıdaki yöntemimde, konsolun dosyaları sağlanan diziyle aynı sırada günlüğe kaydedeceği garanti edilir.


1
Yanlış olduğunuzdan eminim: Yöntemin dosyaları da sıra dışı okuyabileceğinden eminim. Evet, çıktıyı doğru sırayla kaydeder (nedeniyle await Promise.all), ancak dosyalar farklı bir sırayla okunmuş olabilir, bu da ifadenizle çelişir. "Konsolun dosyaları olduğu gibi sırayla günlüğe kaydedeceğinden emin olursunuz "okuyun.
Venryx

1
@Venryx Haklısın, düzeltme için teşekkürler. Cevabımı güncelledim.
chharvey

5

Bergi'nin çözümüfs , söz verildiğinde güzel çalışıyor . Sen kullanabilirsiniz bluebird, fs-extraya da fs-promisebunun için.

Bununla birlikte, düğümün doğal fslibary için çözüm aşağıdaki gibidir:

const result = await Promise.all(filePaths
    .map( async filePath => {
      const fileContents = await getAssetFromCache(filePath, async function() {

        // 1. Wrap with Promise    
        // 2. Return the result of the Promise
        return await new Promise((res, rej) => {
          fs.readFile(filePath, 'utf8', function(err, data) {
            if (data) {
              res(data);
            }
          });
        });
      });

      return fileContents;
    }));

Not: require('fs') zorunlu olarak işlevi 3. bağımsız değişken olarak alır, aksi halde hata atar:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

4

Yukarıdaki çözümlerin ikisi de işe yarıyor, ancak Antonio'nun işi daha az kodla yapıyor, işte benim veritabanımdaki verileri, birkaç farklı çocuk referansından çözmeme ve daha sonra hepsini bir diziye itmeye ve sonuçta bir sözde çözmeme nasıl yardımcı oldu? done:

Promise.all(PacksList.map((pack)=>{
    return fireBaseRef.child(pack.folderPath).once('value',(snap)=>{
        snap.forEach( childSnap => {
            const file = childSnap.val()
            file.id = childSnap.key;
            allItems.push( file )
        })
    })
})).then(()=>store.dispatch( actions.allMockupItems(allItems)))

3

eşzamansız verileri serileştirilmiş bir sırada işleyecek ve kodunuza daha geleneksel bir lezzet verecek bir dosyada birkaç yöntemi patlatmak oldukça acısızdır. Örneğin:

module.exports = function () {
  var self = this;

  this.each = async (items, fn) => {
    if (items && items.length) {
      await Promise.all(
        items.map(async (item) => {
          await fn(item);
        }));
    }
  };

  this.reduce = async (items, fn, initialValue) => {
    await self.each(
      items, async (item) => {
        initialValue = await fn(initialValue, item);
      });
    return initialValue;
  };
};

şimdi bunun './myAsync.js' dosyasında kaydedildiğini varsayarak, bitişik bir dosyada aşağıdakine benzer bir şey yapabilirsiniz:

...
/* your server setup here */
...
var MyAsync = require('./myAsync');
var Cat = require('./models/Cat');
var Doje = require('./models/Doje');
var example = async () => {
  var myAsync = new MyAsync();
  var doje = await Doje.findOne({ name: 'Doje', noises: [] }).save();
  var cleanParams = [];

  // FOR EACH EXAMPLE
  await myAsync.each(['bork', 'concern', 'heck'], 
    async (elem) => {
      if (elem !== 'heck') {
        await doje.update({ $push: { 'noises': elem }});
      }
    });

  var cat = await Cat.findOne({ name: 'Nyan' });

  // REDUCE EXAMPLE
  var friendsOfNyanCat = await myAsync.reduce(cat.friends,
    async (catArray, friendId) => {
      var friend = await Friend.findById(friendId);
      if (friend.name !== 'Long cat') {
        catArray.push(friend.name);
      }
    }, []);
  // Assuming Long Cat was a friend of Nyan Cat...
  assert(friendsOfNyanCat.length === (cat.friends.length - 1));
}

2
Küçük Zeyilname, bekliyor / asyncs deneyin deneyin / blokları yakalamak için !!
Jay Edwards

3

@ Bergi'nin yanıtı gibi, ama bir farkla.

Promise.all biri reddedilirse tüm vaatleri reddeder.

Yani, bir özyineleme kullanın.

const readFilesQueue = async (files, index = 0) {
    const contents = await fs.readFile(files[index], 'utf8')
    console.log(contents)

    return files.length <= index
        ? readFilesQueue(files, ++index)
        : files

}

const printFiles async = () => {
    const files = await getFilePaths();
    const printContents = await readFilesQueue(files)

    return printContents
}

printFiles()

PS

readFilesQueueprintFilesneden olduğu yan etkinin * dışındadır, console.logalay etmek, test etmek veya casusluk yapmak daha iyidir, bu nedenle içeriği döndüren bir fonksiyona sahip olmak güzel değildir (sidenote).

Bu nedenle, kod basitçe şu şekilde tasarlanabilir: "saf" ** olan ve hiçbir yan etki oluşturmayan, tüm listeyi işleyen ve başarısız durumları işlemek için kolayca değiştirilebilir üç ayrı işlev.

const files = await getFilesPath()

const printFile = async (file) => {
    const content = await fs.readFile(file, 'utf8')
    console.log(content)
}

const readFiles = async = (files, index = 0) => {
    await printFile(files[index])

    return files.lengh <= index
        ? readFiles(files, ++index)
        : files
}

readFiles(files)

Gelecekteki düzenleme / mevcut durum

Düğüm üst düzey beklemeyi destekler (bu henüz bir eklentiye sahip değildir, uyum bayrakları ile etkinleştirilemez ve etkinleştirilebilir), havalı ama bir sorunu çözmüyor (stratejik olarak sadece LTS sürümlerinde çalışıyorum). Dosyalar nasıl alınır?

Kompozisyon kullanma. Kod verildiğinde, bunun bir modülün içinde olduğu hissine neden oluyor, bu yüzden bunu yapacak bir işleve sahip olmalı. Değilse, rol kodunu, sizin için her şeyi yapan basit bir modül yaratan bir zaman uyumsuz işlevine sarmak için bir IIFE kullanmalısınız veya doğru yol, kompozisyon var.

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

Değişken adının anlambilim nedeniyle değiştiğini unutmayın. Bir functor (başka bir işlev tarafından çağrılabilen bir işlev) iletir ve bellekte uygulamanın ilk mantık bloğunu içeren bir işaretçi alırsınız.

Ancak, bir modül değilse ve mantığı dışa aktarmanız gerekiyor mu?

İşlevleri zaman uyumsuz bir işleve sarın.

export const readFilesQueue = async () => {
    // ... to code goes here
}

Ya da, değişkenlerin adlarını değiştirin ...


* yan etki ile, uygulamadaki statü / davranışı değiştirebilecek veya uygulamadaki hataları (GÇ gibi) içine sokabilecek herhangi bir kolansiyel etkisini ortaya çıkarır.

** "saf" ile, kesme işareti olduğu için işlevler saf değildir ve kod, konsol çıkışı olmadığında, yalnızca veri manipülasyonları olduğunda saf bir sürüme dönüştürülebilir.

Bunun yanı sıra, saf olmak için, yan etkiyi ele alan, hataya eğilimli olan ve bu hatayı uygulamanın ayrı ayrı ele alan monadlarla çalışmanız gerekir.


2

Önemli bir uyarı : await + for .. ofYöntem ve forEach + asyncyol aslında farklı etkiye sahiptir.

Having awaitgerçek iç fordöngü sağlayacağız tüm zaman uyumsuz aramalar tek tek yürütülür. Ve forEach + asyncyol tüm vaatleri aynı anda tetikleyecek, ki bu daha hızlı ama bazen bunalmış ( eğer bir DB sorgusu yaparsanız veya ses kısıtlamaları olan bazı web hizmetlerini ziyaret ederseniz ve aynı anda 100.000 çağrı yapmak istemiyorsanız).

Ayrıca, dosyaları birbiri ardına okunduğundan emin olmak istemiyorsanız reduce + promise(daha az zarif) de kullanabilirsiniz .async/await

files.reduce((lastPromise, file) => 
 lastPromise.then(() => 
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

Veya yardım etmek için bir forEachAsync oluşturabilir, ancak temelde bunu temeldeki döngü için kullanabilirsiniz.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}

Bir döngü içinde görünmemesi için Array.prototype ve Object.prototype üzerinde javascript yöntemini tanımlamak için bir göz atın . Ayrıca forEach, yinelenebilirliğe güvenmek yerine yerel erişimli dizinlerle aynı yinelemeyi kullanmanız ve dizini geri aramaya geçirmeniz gerekir.
Bergi

Zaman Array.prototype.reduceuyumsuz işlevini kullanan bir şekilde kullanabilirsiniz .
Timothy Zorn

2

Görev, fütürize etme ve sürülebilir bir Liste kullanarak,

async function printFiles() {
  const files = await getFiles();

  List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
    .fork( console.error, console.log)
}

Bunu nasıl ayarlayacağınız

import fs from 'fs';
import { futurize } from 'futurize';
import Task from 'data.task';
import { List } from 'immutable-ext';

const future = futurizeP(Task)
const readFile = future(fs.readFile)

İstenen kodu yapılandırmanın başka bir yolu,

const printFiles = files => 
  List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
    .fork( console.error, console.log)

Ya da belki daha işlevsel olarak

// 90% of encodings are utf-8, making that use case super easy is prudent

// handy-library.js
export const readFile = f =>
  future(fs.readFile)( f, 'utf-8' )

export const arrayToTaskList = list => taskFn => 
  List(files).traverse( Task.of, taskFn ) 

export const readFiles = files =>
  arrayToTaskList( files, readFile )

export const printFiles = files => 
  readFiles(files).fork( console.error, console.log)

Sonra üst işlevden

async function main() {
  /* awesome code with side-effects before */
  printFiles( await getFiles() );
  /* awesome code with side-effects after */
}

Kodlamada gerçekten daha fazla esneklik istiyorsanız, bunu yapabilirsiniz (eğlence için önerilen Boru İletme operatörünü kullanıyorum )

import { curry, flip } from 'ramda'

export const readFile = fs.readFile 
  |> future,
  |> curry,
  |> flip

export const readFileUtf8 = readFile('utf-8')

PS - Konsolda bu kodu denemedim, bazı yazım hataları olabilir ... "düz serbest stil, kubbenin üst kapalı!" 90'lı yıllarda çocukların dediği gibi. :-p


2

Şu anda Array.forEach prototip özelliği eşzamansız işlemleri desteklememektedir, ancak ihtiyaçlarımızı karşılamak için kendi çoklu dolgumuzu oluşturabiliriz.

// Example of asyncForEach Array poly-fill for NodeJs
// file: asyncForEach.js
// Define asynForEach function 
async function asyncForEach(iteratorFunction){
  let indexer = 0
  for(let data of this){
    await iteratorFunction(data, indexer)
    indexer++
  }
}
// Append it as an Array prototype property
Array.prototype.asyncForEach = asyncForEach
module.exports = {Array}

Ve bu kadar! Artık bunlardan sonra işlemler için tanımlanan tüm dizilerde zaman uyumsuz bir forEach yönteminiz var.

Test edelim ...

// Nodejs style
// file: someOtherFile.js

const readline = require('readline')
Array = require('./asyncForEach').Array
const log = console.log

// Create a stream interface
function createReader(options={prompt: '>'}){
  return readline.createInterface({
    input: process.stdin
    ,output: process.stdout
    ,prompt: options.prompt !== undefined ? options.prompt : '>'
  })
}
// Create a cli stream reader
async function getUserIn(question, options={prompt:'>'}){
  log(question)
  let reader = createReader(options)
  return new Promise((res)=>{
    reader.on('line', (answer)=>{
      process.stdout.cursorTo(0, 0)
      process.stdout.clearScreenDown()
      reader.close()
      res(answer)
    })
  })
}

let questions = [
  `What's your name`
  ,`What's your favorite programming language`
  ,`What's your favorite async function`
]
let responses = {}

async function getResponses(){
// Notice we have to prepend await before calling the async Array function
// in order for it to function as expected
  await questions.asyncForEach(async function(question, index){
    let answer = await getUserIn(question)
    responses[question] = answer
  })
}

async function main(){
  await getResponses()
  log(responses)
}
main()
// Should prompt user for an answer to each question and then 
// log each question and answer as an object to the terminal

Harita gibi diğer dizi işlevleri için de aynısını yapabilirdik ...

async function asyncMap(iteratorFunction){
  let newMap = []
  let indexer = 0
  for(let data of this){
    newMap[indexer] = await iteratorFunction(data, indexer, this)
    indexer++
  }
  return newMap
}

Array.prototype.asyncMap = asyncMap

... ve bunun gibi :)

Dikkat edilmesi gereken bazı noktalar:

  • İteratorFonksiyonunuz bir eşzamansız işlev veya vaat olmalıdır
  • Daha önce oluşturulan Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>hiçbir dizide bu özellik bulunmayacak

2

Sadece orijinal cevaba ekleyerek

  • Orijinal cevaptaki paralel okuma sözdizimi bazen kafa karıştırıcıdır ve okunması zordur, belki farklı bir yaklaşımla yazabiliriz
async function printFiles() {
  const files = await getFilePaths();
  const fileReadPromises = [];

  const readAndLogFile = async filePath => {
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
    return contents;
  };

  files.forEach(file => {
    fileReadPromises.push(readAndLogFile(file));
  });

  await Promise.all(fileReadPromises);
}
  • Sıralı çalışma için, sadece ... değil , döngü için normal de çalışacaktır
async function printFiles() {
  const files = await getFilePaths();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
  }
}

1

Bugün bunun için birçok çözümle karşılaştım. ForEach Loop'ta zaman uyumsuzluğu çalıştırma işlevlerini bekliyor. Sargıyı etrafına inşa ederek bunu yapabiliriz.

Dahili olarak, yerel forEach için nasıl çalıştığı ve neden zaman uyumsuz işlev çağrısı yapamadığına ve çeşitli yöntemlerle ilgili diğer ayrıntılara ilişkin daha ayrıntılı açıklama burada bağlantıda verilmiştir.

Bunun yapılabileceği çoklu yollar ve bunlar aşağıdaki gibidir,

Yöntem 1: Sarıcı kullanma.

await (()=>{
     return new Promise((resolve,reject)=>{
       items.forEach(async (item,index)=>{
           try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
           count++;
           if(index === items.length-1){
             resolve('Done')
           }
         });
     });
    })();

Yöntem 2: Array.prototype genel bir işlevle aynı kullanma

Array.prototype.forEachAsync.js

if(!Array.prototype.forEachAsync) {
    Array.prototype.forEachAsync = function (fn){
      return new Promise((resolve,reject)=>{
        this.forEach(async(item,index,array)=>{
            await fn(item,index,array);
            if(index === array.length-1){
                resolve('done');
            }
        })
      });
    };
  }

Kullanımı:

require('./Array.prototype.forEachAsync');

let count = 0;

let hello = async (items) => {

// Method 1 - Using the Array.prototype.forEach 

    await items.forEachAsync(async () => {
         try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
        count++;
    });

    console.log("count = " + count);
}

someAPICall = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("done") // or reject('error')
        }, 100);
    })
}

hello(['', '', '', '']); // hello([]) empty array is also be handled by default

Yöntem 3:

Promise.all kullanma

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);

Yöntem 4: Döngü için geleneksel veya döngü için modern

// Method 4 - using for loop directly

// 1. Using the modern for(.. in..) loop
   for(item in items){

        await someAPICall();
        count++;
    }

//2. Using the traditional for loop 

    for(let i=0;i<items.length;i++){

        await someAPICall();
        count++;
    }


    console.log("count = " + count);

Yöntem 1 ve 2, Promise.allkullanılması gereken yanlış uygulamalardır - birçok uç durumdan hiçbirini dikkate almazlar.
Bergi

@Bergi: Geçerli yorumlar için teşekkürler, Yöntem 1 ve 2'nin neden yanlış olduğunu açıklar mısınız? Ayrıca amaca hizmet eder. Bu çok iyi çalışıyor. Bu, tüm bu yöntemlerin mümkün olduğu anlamına gelir, birinin seçimine karar verebileceği duruma göre. Aynı çalışan örnek var.
PranavKAndro

Boş dizilerde başarısız olur, hata işleme ve muhtemelen daha fazla sorun yoktur. Tekerleği yeniden icat etme. Sadece kullan Promise.all.
Bergi

Mümkün olmadığı bazı durumlarda yardımcı olacaktır. Ayrıca hata işleme varsayılan olarak forEach api tarafından yapılır, bu yüzden hiçbir sorun. Onun bakımı!
PranavKAndro

Hayır, Promise.allmümkün olmayan async/ mümkün olmayan koşullar yoktur await. Ve hayır, forEachkesinlikle herhangi bir söz hatası işlemez.
Bergi

1

Bu çözüm aynı zamanda bellek için optimize edilmiştir, böylece 10.000'in üzerinde veri öğesi ve isteği üzerinde çalıştırabilirsiniz. Buradaki diğer çözümlerden bazıları, sunucuyu büyük veri kümelerinde kilitleyecektir.

TypeScript'te:

export async function asyncForEach<T>(array: Array<T>, callback: (item: T, index: number) => void) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index);
        }
    }

Nasıl kullanılır?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})

1

Kullanabilirsiniz Array.prototype.forEach, ancak async / await çok uyumlu değil. Bunun nedeni, zaman uyumsuz geri çağrıdan döndürülen vaatin çözümlenmeyi beklemesi, ancak geri çağrısının Array.prototype.forEachyürütülmesinden vaat edilmemesidir. Öyleyse, forEach'ı kullanabilirsiniz, ancak vaat çözümünü kendiniz ele almanız gerekir.

İşte her dosyayı seri olarak okumak ve yazdırmak için bir yol Array.prototype.forEach

async function printFilesInSeries () {
  const files = await getFilePaths()

  let promiseChain = Promise.resolve()
  files.forEach((file) => {
    promiseChain = promiseChain.then(() => {
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    })
  })
  await promiseChain
}

Array.prototype.forEachDosyaların içeriğini paralel olarak yazdırmanın bir yolu (hala kullanılıyor )

async function printFilesInParallel () {
  const files = await getFilePaths()

  const promises = []
  files.forEach((file) => {
    promises.push(
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    )
  })
  await Promise.all(promises)
}

İlk senaryo, seri halinde çalıştırılması gereken döngüler için idealdir ve kullanamazsınız
Mark Odey

0

Antonio p-iterationVal's'e benzer bir alternatif npm modülü async-af:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

function printFiles() {
  // since AsyncAF accepts promises or non-promises, there's no need to await here
  const files = getFilePaths();

  AsyncAF(files).forEach(async file => {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  });
}

printFiles();

Alternatif olarak, async-afvaatlerin sonuçlarını kaydeden statik bir yöntem (log / logAF) vardır:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

function printFiles() {
  const files = getFilePaths();

  AsyncAF(files).forEach(file => {
    AsyncAF.log(fs.readFile(file, 'utf8'));
  });
}

printFiles();

Ancak, kütüphanenin ana avantajı, asenkron yöntemleri aşağıdaki gibi bir şey yapmak için zincirleyebilmenizdir:

const aaf = require('async-af');
const fs = require('fs-promise');

const printFiles = () => aaf(getFilePaths())
  .map(file => fs.readFile(file, 'utf8'))
  .forEach(file => aaf.log(file));

printFiles();

async-af


0

Bunun nasıl yanlış gidebileceğini görmek için yöntemin sonunda console.log yazdırın.

Genel olarak yanlış gidebilecek şeyler:

  • Keyfi düzen.
  • printFiles dosyaları yazdırmadan önce çalışmayı bitirebilir.
  • Zayıf performans.

Bunlar her zaman yanlış değildir ancak sıklıkla standart kullanım durumlarındadır.

Genellikle, forEach kullanımı sonuncusu dışında her şeyle sonuçlanır. Fonksiyonu beklemeden her fonksiyonu çağırır, yani tüm fonksiyonların başlamasını söyler ve fonksiyonların bitmesini beklemeden biter.

import fs from 'fs-promise'

async function printFiles () {
  const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'))

  for(const file of files)
    console.log(await file)
}

printFiles()

Bu, yerel JS'de düzeni koruyan, işlevin erken dönmesini önleyen ve teoride en iyi performansı koruyan bir örnektir.

Bu irade:

  • Tüm dosya okumalarını paralel olarak başlatın.
  • Bekleneceği vaat edilen dosya adlarını eşlemek için haritayı kullanarak siparişi koruyun.
  • Her vaat için dizi tarafından tanımlanan sırada bekleyin.

Bu çözümle, ilk dosya, diğerlerinin ilk önce kullanılabilir olmasını beklemek zorunda kalmadan en kısa sürede gösterilecektir.

Ayrıca, ikinci dosya okunmaya başlamadan önce ilkinin bitmesini beklemek yerine tüm dosyaları aynı anda yükleyecektir.

Bunun ve orijinal versiyonun tek dezavantajı, aynı anda birden fazla okuma başlatılırsa, aynı anda olabilecek daha fazla hata olması nedeniyle hataları işlemenin daha zor olmasıdır.

Bir kerede bir dosyayı okuyan sürümlerle, daha fazla dosyayı okumaya çalışırken zaman kaybetmeden bir arızada durur. Ayrıntılı bir iptal sistemiyle bile, ilk dosyada başarısız olmasını önlemek, ancak diğer dosyaların çoğunu zaten okumaktan kaçınmak zor olabilir.

Performans her zaman öngörülebilir değildir. Birçok sistem paralel dosya okumaları ile daha hızlı olurken, bazıları sıralı tercih eder. Bazıları dinamiktir ve yük altında kayabilir, gecikme sunan optimizasyonlar ağır çekişme altında her zaman iyi verim vermez.

Bu örnekte hata işleme de yoktur. Bir şey bunların hepsinin başarıyla gösterilmesini veya hiç gösterilmemesini gerektiriyorsa, bunu yapmayacaktır.

Her aşamada console.log ve sahte dosya okuma çözümleri (bunun yerine rastgele gecikme) ile derinlemesine deneme yapılması önerilir. Her ne kadar birçok çözüm basit durumlarda aynı şeyi yapıyor gibi görünse de, hepsinin sıkmak için ekstra inceleme gerektiren ince farklılıkları vardır.

Çözümler arasındaki farkı anlatmaya yardımcı olması için bu taklidi kullanın:

(async () => {
  const start = +new Date();
  const mock = () => {
    return {
      fs: {readFile: file => new Promise((resolve, reject) => {
        // Instead of this just make three files and try each timing arrangement.
        // IE, all same, [100, 200, 300], [300, 200, 100], [100, 300, 200], etc.
        const time = Math.round(100 + Math.random() * 4900);
        console.log(`Read of ${file} started at ${new Date() - start} and will take ${time}ms.`)
        setTimeout(() => {
          // Bonus material here if random reject instead.
          console.log(`Read of ${file} finished, resolving promise at ${new Date() - start}.`);
          resolve(file);
        }, time);
      })},
      console: {log: file => console.log(`Console Log of ${file} finished at ${new Date() - start}.`)},
      getFilePaths: () => ['A', 'B', 'C', 'D', 'E']
    };
  };

  const printFiles = (({fs, console, getFilePaths}) => {
    return async function() {
      const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'));

      for(const file of files)
        console.log(await file);
    };
  })(mock());

  console.log(`Running at ${new Date() - start}`);
  await printFiles();
  console.log(`Finished running at ${new Date() - start}`);
})();

-3

İyi test edilmiş (haftada milyonlarca indirme) pify ve async modülünü kullanırım. Async modülünü bilmiyorsanız , belgelerine göz atmanızı şiddetle tavsiye ederim . Birden fazla devs yöntemlerini yeniden yaratan zaman kaybettim, ya da daha kötüsü, daha yüksek dereceli asenkron yöntemler kodu basitleştirecek zaman korumak için zaman uyumsuz kod yapma.

const async = require('async')
const fs = require('fs-promise')
const pify = require('pify')

async function getFilePaths() {
    return Promise.resolve([
        './package.json',
        './package-lock.json',
    ]);
}

async function printFiles () {
  const files = await getFilePaths()

  await pify(async.eachSeries)(files, async (file) => {  // <-- run in series
  // await pify(async.each)(files, async (file) => {  // <-- run in parallel
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
  console.log('HAMBONE')
}

printFiles().then(() => {
    console.log('HAMBUNNY')
})
// ORDER OF LOGS:
// package.json contents
// package-lock.json contents
// HAMBONE
// HAMBUNNY
```


Bu yanlış yönde atılmış bir adım. İşte milletlerin modern JS çağına geri dönmelerine engel olmak için oluşturduğum bir haritalama kılavuzu: github.com/jmjpro/async-package-to-async-await/blob/master/… .
jbustamovej

Burada gördüğünüz gibi , async lib yerine async / await kullanmaya ilgi duyuyorum. Şu anda her birinin bir zamanı ve yeri olduğunu düşünüyorum. Ben async lib == "geri arama cehennem" ve async / await == "modern JS dönemi" olduğuna ikna olmadım. imo, zaman async lib> zaman uyumsuz / bekliyor: 1. karmaşık akış (örneğin, kuyruk, kargo, hatta işler karmaşıklaştığında otomatik bile) 2. eşzamanlılık 3. destek diziler / nesneler / yinelenebilir 4. hata işleme
Zachary Ryan Smith
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.