2016-06-01 2 views
265

Есть ли проблемы с использованием async/await в цикле forEach? Я пытаюсь перебрать массив файлов и await на содержимое каждого файла.Использование async/wait с циклом forEach

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

Этот код действительно работает, но может быть что-то не так с этим? Мне кто-то сказал, что вы не должны использовать async/await в более высоком порядке, как это, поэтому я просто хотел спросить, была ли проблема с этим.

ответ

535

Уверенный, что код действительно работает, но я уверен, что он не делает то, что вы ожидаете от него. Он просто запускает несколько асинхронных вызовов, но функция printFiles сразу же возвращается после этого.

Если вы хотите последовательно просмотреть файлы, , вы не можете использовать forEach. Просто используйте современную for … of петлю вместо, в котором await будет работать, как ожидалось:

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

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

Если вы хотите читать файлы параллельно, вы не можете использовать forEach действительно. Каждый из вызовов функции обратного вызовавозвращает обещание, но вы отбрасываете их, а не ожидаете их. Просто используйте map вместо этого, и вы можете ждать массив обещаний, которые вы получите с 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) 
    })); 
} 
+6

Не могли бы вы объяснить, почему работает 'for ... of ...'? – Demonbane

+14

ok Я знаю, почему ... Использование Babel преобразует 'async' /' await' в функцию генератора, а использование 'forEach' означает, что каждая итерация имеет отдельную функцию генератора, которая не имеет ничего общего с другими. поэтому они будут выполняться независимо и не имеют контекста 'next()' с другими.Фактически, простой цикл 'for()' также работает, потому что итерации также находятся в одной функции генератора. – Demonbane

+3

@ Demonbane: Короче говоря, потому что он был разработан для работы :-) 'await' приостанавливает текущую * функцию * оценку, включая все структуры управления. Да, он очень похож на генераторы в этом отношении (поэтому они используются для полифония async/await). – Bergi

8

Для меня, используя Promise.all() с map() немного трудно понять и многословным, но если вы хотите, чтобы это сделать в простой JS, это ваш лучший выстрел, я думаю.

Если вы не возражаете против добавления модуля, я применил методы итерации массива, чтобы их можно было использовать очень просто с помощью async/await.

Пример с вашим делом:

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

printFiles() 

p-iteration

+2

Ничего себе, p-итерация настолько гладкая. Спас мой день! –

0

Оба решения выше работы, однако, Антонио делает работу с меньшим количеством кода, вот как это помогло мне решить данные из моей базы данных, из нескольких дочерних рефов, а затем толкает их все в массив и его разрешения в обещание в конце концов сделано:

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

это довольно безболезненно вывести пару методов в файл, который будет обрабатывать асинхронные данные в сериализованном порядке и придать вашему стандарту более традиционный вкус. Например:

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

сейчас, если предположить, что спасена в»./myAsync.js' вы можете сделать что-то похожее на ниже в соседнем файле:

... 
/* 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)); 
} 
+1

Незначительное дополнение, не забудьте обернуть ваш ожидание/асинк в блоках try/catch! –

0

Одним из важных предостережение является: Метод await + for .. of и способ forEach + async фактически имеют разный эффект.

Имея await внутри реального цикла for, убедитесь, что все асинхронные вызовы выполняются один за другим.И способ forEach + async будет сбрасывать все обещания в одно и то же время, что быстрее, но иногда перегружается (, если вы делаете какой-либо запрос БД или посещаете некоторые веб-сервисы с ограничениями по объему и не хотите запускать 100 000 звонков за раз).

Вы также можете использовать reduce + promise (менее элегантно), если вы не используете async/await и хотите, чтобы убедиться, что файлы считываются один за другим.

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

Или вы можете создать forEachAsync, чтобы помочь, но в основном использовать то же самое для цикла.

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

Посмотрите на [Как определить метод в javascript на Array.prototype и Object.prototype, чтобы он не отображался в цикле] (https://stackoverflow.com/q/13296340/1048572). Также вам, вероятно, следует использовать ту же самую итерацию, что и native 'forEach' - доступ к индексам вместо того, чтобы полагаться на итеративность - и передать индекс обратному вызову. – Bergi

-1

Я бы использовать хорошо проверенные (миллионы загрузок в неделю) pify и async модули. Если вы не знакомы с асинхронным модулем, я настоятельно рекомендую вам проверить its docs. Я видел, как несколько разработчиков теряют время, воссоздавая свои методы, или, что еще хуже, делают сложный в обслуживании асинхронный код, когда асинхронные методы более высокого порядка упрощают код.

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

+0

Это шаг в неправильном направлении. Вот руководство по составлению карт, которое я создал, чтобы помочь людям застрять в адском обратном пути в современную эпоху JS: https://github.com/jmjpro/async-package-to-async-await/blob/master/README.md. – jbustamovej

+0

, как вы [можете видеть здесь] (https://github.com/ZacharyRSmith/javascript-async-await-promise-katas/pull/6), меня интересует и открыта для использования async/await вместо async lib , Прямо сейчас, я думаю, что у каждого есть время и место. Я не уверен, что async lib == «callback hell» и async/await == «современная эпоха JS». имо, когда асинхронная Lib> асинхронного/Await: 1. сложный поток (например, очередь, грузы, даже авто, когда все становится сложнее) 2. параллелизма 3. поддерживающие массивы/объектов/итерируемые обработки 4. эээ –

0

В дополнение к @Bergi’s answer, я хотел бы предложить третью альтернативу. Это очень похоже на второй пример @ Берги, но вместо того, чтобы ждать каждого readFile отдельно, вы создаете массив обещаний, каждый из которых вы ожидаете в конце.

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

    let promises = [] 
    for (let i = 0; i < files.length; i++) { 
    // push the promise to the array 
    promises.push(fs.readFile(file, 'utf8').then(console.log)) 
    } 
    await Promise.all(promises) 
} 

или консольный их в журнал все в конце:

let promises = [] 
    for (let i = 0; i < files.length; i++) { 
    // push the promise to the array 
    promises.push(fs.readFile(file, 'utf8')) 
    } 
    let results = await Promise.all(promises) 
    results.forEach(console.log) 
1

Использование задачи, futurize, и проходимую список, вы можете просто сделать

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

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

Вот как вы» d установить это значение

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) 

Anot ее способ структурировали нужный код будет

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

Или, возможно, даже более функционально ориентированных

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

Тогда из родительской функции

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

Если вы действительно хотели больше гибкости в кодировании , вы могли бы просто сделать это

import { curry, flip , compose } from 'ramda' 

export const readFile = f => compose (
    flip, 
    curry, 
    future, 
    fs.readFile 
) 

const readFileUtf8 = readFile('utf-8') 

PS - Я не пробовал этот код на консоли, возможно, некоторые опечатки ... «прямой фристайл, с вершины купола!» как сказали бы дети 90-х годов. :-p

Смежные вопросы