Sử dụng async / await với vòng lặp forEach

1405
Saad 2016-06-02 08:55.

Có bất kỳ vấn đề nào với việc sử dụng async/ awaittrong một forEachvòng lặp không? Tôi đang cố gắng lặp lại một loạt tệp và awaitnội dung của mỗi tệp.

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

Mã này hoạt động, nhưng có thể xảy ra sự cố với điều này? Tôi đã có người nói với tôi rằng bạn không được phép sử dụng async/ awaitở một hàm thứ tự cao hơn như thế này, vì vậy tôi chỉ muốn hỏi xem có vấn đề gì với điều này không.

19 answers

2680
Bergi 2016-06-02 09:02.

Chắc chắn mã hoạt động, nhưng tôi khá chắc chắn rằng nó không làm những gì bạn mong đợi. Nó chỉ kích hoạt nhiều lệnh gọi không đồng bộ, nhưng printFileshàm sẽ trả về ngay lập tức sau đó.

Đọc theo trình tự

Nếu bạn muốn đọc các tệp theo trình tự, bạn không thể sử dụngforEach thực sự. for … ofThay vào đó, chỉ cần sử dụng một vòng lặp hiện đại , trong đó awaitsẽ hoạt động như mong đợi:

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

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

Đọc song song

Nếu bạn muốn đọc các tệp song song, bạn không thể sử dụngforEach thực sự. Mỗi lệnh asyncgọi hàm gọi lại trả về một lời hứa, nhưng bạn đang vứt bỏ chúng thay vì chờ đợi chúng. Chỉ cần sử dụng mapthay thế và bạn có thể chờ đợi một loạt các lời hứa mà bạn sẽ nhận được 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)
  }));
}
274
Francisco Mateo 2018-06-16 01:17.

Với ES2018, bạn có thể đơn giản hóa tất cả các câu trả lời trên để:

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

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

Xem thông số kỹ thuật: đề xuất-không đồng bộ-lặp lại


2018-09-10: Câu trả lời này gần đây đã nhận được rất nhiều sự chú ý, vui lòng xem bài đăng trên blog của Axel Rauschmayer để biết thêm thông tin về lặp lại không đồng bộ: ES2018: lặp lại không đồng bộ

81
Timothy Zorn 2018-03-27 09:48.

Thay vì Promise.allkết hợp với Array.prototype.map(không đảm bảo thứ tự mà các Promises được giải quyết), tôi sử dụng Array.prototype.reduce, bắt đầu với một đã giải quyết Promise:

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());
}
35
Antonio Val 2017-07-10 22:15.

- đun lặp p trên npm triển khai các phương thức lặp mảng để chúng có thể được sử dụng một cách rất đơn giản với async / await.

Một ví dụ với trường hợp của bạn:

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);
  });
})();
32
Matt 2018-03-23 05:11.

Đây là một số forEachAsyncnguyên mẫu. Lưu ý rằng bạn sẽ cầ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));
}

Lưu ý rằng trong khi bạn có thể bao gồm điều này trong mã của riêng mình, bạn không nên đưa nó vào các thư viện mà bạn phân phối cho người khác (để tránh làm ô nhiễm toàn cầu của họ).

9
chharvey 2018-02-23 14:47.

Ngoài câu trả lời của @ Bergi , tôi muốn đưa ra một giải pháp thay thế thứ ba. Nó rất giống với ví dụ thứ 2 của @ Bergi, nhưng thay vì chờ đợi từng cái readFileriêng lẻ, bạn tạo ra một loạt các lời hứa, mỗi lời hứa mà bạn chờ đợi ở cuối.

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

Lưu ý rằng hàm được truyền tới .map()không cần phải như vậy async, vì vẫn fs.readFiletrả về một đối tượng Promise. Do đó, promiseslà một mảng các đối tượng Promise, có thể được gửi đến Promise.all().

Trong câu trả lời của @ Bergi, bảng điều khiển có thể ghi lại nội dung tệp theo thứ tự chúng được đọc. Ví dụ: nếu một tệp thực sự nhỏ kết thúc việc đọc trước một tệp thực sự lớn, nó sẽ được ghi đầu tiên, ngay cả khi tệp nhỏ đến sau tệp lớn trong filesmảng. Tuy nhiên, trong phương pháp của tôi ở trên, bạn được đảm bảo rằng bảng điều khiển sẽ ghi nhật ký các tệp theo thứ tự giống như mảng được cung cấp.

7
master_dodo 2019-05-27 12:08.

Giải pháp của Bergi hoạt động tốt khi fsdựa trên lời hứa. Bạn có thể sử dụng bluebird, fs-extrahoặc fs-promisecho điều này.

Tuy nhiên, giải pháp chofs libary gốc của nút như sau:

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

Lưu ý: require('fs') bắt buộc sử dụng hàm làm đối số thứ 3, nếu không sẽ tạo ra lỗi:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
6
Hooman Askari 2017-08-27 00:47.

Cả hai giải pháp trên đều hoạt động, tuy nhiên, Antonio thực hiện công việc với ít mã hơn, đây là cách nó giúp tôi giải quyết dữ liệu từ cơ sở dữ liệu của mình, từ một số tham chiếu con khác nhau và sau đó đẩy tất cả chúng vào một mảng và giải quyết nó theo một lời hứa sau cùng là làm xong:

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)))
5
Jay Edwards 2017-09-23 13:03.

khá dễ dàng để bật một vài phương thức trong một tệp sẽ xử lý dữ liệu không đồng bộ theo thứ tự được tuần tự hóa và mang lại hương vị thông thường hơn cho mã của bạn. Ví dụ:

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

bây giờ, giả sử được lưu tại './myAsync.js', bạn có thể làm điều gì đó tương tự như bên dưới trong một tệp liền kề:

...
/* 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));
}
5
Oliver Dixon 2020-04-17 07:18.

Giải pháp này cũng được tối ưu hóa bộ nhớ để bạn có thể chạy nó trên 10.000 mục dữ liệu và yêu cầu. Một số giải pháp khác ở đây sẽ làm sập máy chủ trên các tập dữ liệu lớn.

Trong TypeScript:

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

Cách sử dụng?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})
4
LeOn - Han Li 2017-09-25 10:00.

Một lưu ý quan trọng là: await + for .. ofPhương pháp và forEach + asynccách thức thực sự có tác dụng khác nhau.

awaitbên trong một forvòng lặp thực sẽ đảm bảo tất cả các lệnh gọi không đồng bộ được thực hiện từng cái một. Và forEach + asynccách này sẽ thực hiện tất cả các lời hứa cùng một lúc, nhanh hơn nhưng đôi khi quá tải ( nếu bạn thực hiện một số truy vấn DB hoặc truy cập một số dịch vụ web có giới hạn số lượng và không muốn kích hoạt 100.000 cuộc gọi cùng một lúc).

Bạn cũng có thể sử dụng reduce + promise(ít thanh lịch hơn) nếu bạn không sử dụng async/awaitvà muốn đảm bảo các tệp được đọc lần lượt .

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

Hoặc bạn có thể tạo forEachAsync để trợ giúp nhưng về cơ bản sử dụng vòng lặp for bên dưới tương tự.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}
4
gsaandy 2019-12-02 06:59.

Chỉ thêm vào câu trả lời ban đầu

  • Cú pháp đọc song song trong câu trả lời gốc đôi khi khó hiểu và khó đọc, có lẽ chúng ta có thể viết nó theo một cách tiếp cận khác
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);
}

  • Đối với hoạt động tuần tự, không chỉ for ... of , vòng lặp for thông thường cũng sẽ hoạt động
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);
  }
}

4
lukaswilkeer 2019-12-21 15:11.

Giống như phản hồi của @ Bergi, nhưng có một điểm khác biệt.

Promise.all từ chối mọi lời hứa nếu một người bị từ chối.

Vì vậy, hãy sử dụng một đệ quy.

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

readFilesQueuekhông nằm ngoài printFilesnguyên nhân gây ra tác dụng phụ * được giới thiệu bởi console.log, tốt hơn là bạn nên mô phỏng, kiểm tra và hoặc do thám vì vậy, không hay ho gì khi có một hàm trả về nội dung (sidenote).

Do đó, mã có thể được thiết kế đơn giản theo đó: ba chức năng riêng biệt "thuần túy" ** và không có tác dụng phụ, xử lý toàn bộ danh sách và có thể dễ dàng sửa đổi để xử lý các trường hợp không thành công.

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)

Chỉnh sửa trong tương lai / trạng thái hiện tại

Node hỗ trợ chờ đợi cấp cao nhất (cái này chưa có plugin, sẽ không có và có thể được kích hoạt thông qua cờ hòa hợp), nó rất tuyệt nhưng không giải quyết được một vấn đề (về mặt chiến lược, tôi chỉ làm việc trên các phiên bản LTS). Làm thế nào để lấy các tập tin?

Sử dụng thành phần. Với mã, gây ra cho tôi cảm giác rằng đây là bên trong một mô-đun, vì vậy, nên có một chức năng để làm điều đó. Nếu không, bạn nên sử dụng IIFE để bọc mã vai trò thành một hàm không đồng bộ tạo mô-đun đơn giản làm tất cả cho bạn hoặc bạn có thể đi theo cách phù hợp, có, thành phần.

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

Lưu ý rằng tên của biến thay đổi do ngữ nghĩa. Bạn truyền một hàm (một hàm có thể được gọi bởi một hàm khác) và nhận một con trỏ trên bộ nhớ có chứa khối logic ban đầu của ứng dụng.

Nhưng, nếu không phải là một mô-đun và bạn cần xuất logic?

Gói các chức năng trong một chức năng không đồng bộ.

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

Hoặc thay đổi tên của các biến, bất cứ điều gì ...


* bởi tác dụng phụ ngăn chặn bất kỳ hiệu ứng vi khuẩn nào của ứng dụng có thể thay đổi trạng thái / hành vi hoặc các lỗi rắc rối trong ứng dụng, như IO.

** bởi "pure", nó ở dạng dấu nháy đơn vì các hàm của nó không thuần túy và mã có thể được chuyển đổi thành phiên bản thuần túy, khi không có đầu ra bảng điều khiển, chỉ có các thao tác dữ liệu.

Ngoài ra, để thuần túy, bạn sẽ cần phải làm việc với các monads xử lý tác dụng phụ, dễ xảy ra lỗi và xử lý lỗi đó riêng của ứng dụng.

3
Babakness 2018-02-28 18:41.

Sử dụng Tác vụ, tương lai và Danh sách có thể duyệt, bạn chỉ cần làm

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

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

Đây là cách bạn thiết lập điều này

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)

Một cách khác để cấu trúc mã mong muốn sẽ là

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

Hoặc thậm chí có thể định hướng chức năng hơn

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

Sau đó, từ hàm cha

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

Nếu bạn thực sự muốn linh hoạt hơn trong việc mã hóa, bạn có thể làm điều này (cho vui, tôi đang sử dụng toán tử Chuyển tiếp ống được đề xuất )

import { curry, flip } from 'ramda'

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

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

Tái bút - Tôi đã không thử mã này trên bảng điều khiển, có thể có một số lỗi chính tả ... "thẳng tự do, lệch khỏi đỉnh của mái vòm!" như những đứa trẻ những năm 90 sẽ nói. :-p

3
Beau 2019-03-13 13:31.

Hiện tại, thuộc tính nguyên mẫu Array.forEach không hỗ trợ các hoạt động không đồng bộ, nhưng chúng tôi có thể tạo đa điền của riêng mình để đáp ứng nhu cầu của chúng tôi.

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

Và đó là nó! Bây giờ bạn có một phương thức async forEach có sẵn trên bất kỳ mảng nào được định nghĩa sau các phép toán này.

Hãy kiểm tra nó ...

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

Chúng ta có thể làm tương tự đối với một số hàm mảng khác như bản đồ ...

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

... và như thế :)

Một số điều cần lưu ý:

  • IteratorFunction của bạn phải là một hàm không đồng bộ hoặc một lời hứa
  • Bất kỳ mảng nào được tạo trước đó Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>sẽ không có sẵn tính năng này
3
PranavKAndro 2019-11-25 10:31.

Hôm nay tôi đã tìm thấy nhiều giải pháp cho điều này. Chạy các chức năng async đang chờ trong forEach Loop. Bằng cách xây dựng lớp bao bọc xung quanh, chúng ta có thể biến điều này thành hiện thực.

Giải thích chi tiết hơn về cách nó hoạt động bên trong, cho forEach gốc và tại sao nó không thể thực hiện lệnh gọi hàm không đồng bộ và các chi tiết khác về các phương thức khác nhau được cung cấp trong liên kết tại đây

Nhiều cách có thể được thực hiện và chúng như sau,

Phương pháp 1: Sử dụng trình bao bọc.

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

Phương pháp 2: Sử dụng giống như một hàm chung của Array.prototype

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

Sử dụng :

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

Phương pháp 3:

Sử dụng Promise.all

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

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

Phương pháp 4: Vòng lặp for truyền thống hoặc vòng lặp for hiện đại

// 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);
3
richytong 2020-05-21 10:57.

Bạn có thể sử dụng Array.prototype.forEach, nhưng async / await không tương thích như vậy. Điều này là do lời hứa được trả về từ một lệnh gọi lại không đồng bộ mong đợi được giải quyết, nhưng Array.prototype.forEachkhông giải quyết bất kỳ lời hứa nào từ việc thực hiện lệnh gọi lại của nó. Vì vậy, bạn có thể sử dụng forEach, nhưng bạn sẽ phải tự xử lý giải quyết lời hứa.

Đây là một cách để đọc và in từng tệp hàng loạt bằng cách sử dụng 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
}

Đây là một cách (vẫn đang sử dụng Array.prototype.forEach) để in song song nội dung của các tệp

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)
}
2
jgmjgm 2019-10-15 08:35.

Để xem điều đó có thể xảy ra sai như thế nào, hãy in console.log ở cuối phương pháp.

Những điều có thể xảy ra nói chung:

  • Lệnh tùy tiện.
  • printFiles có thể chạy xong trước khi in các tập tin.
  • Hiệu suất kém.

Những điều này không phải lúc nào cũng sai nhưng thường là trong các trường hợp sử dụng tiêu chuẩn.

Nói chung, sử dụng forEach sẽ cho kết quả tất cả, trừ trường hợp cuối cùng. Nó sẽ gọi từng hàm mà không cần chờ hàm có nghĩa là nó yêu cầu tất cả các hàm bắt đầu rồi kết thúc mà không cần đợi các hàm kết thúc.

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

Đây là một ví dụ trong JS gốc sẽ duy trì trật tự, ngăn hàm trở lại sớm và về lý thuyết vẫn giữ được hiệu suất tối ưu.

Điều này sẽ:

  • Bắt đầu tất cả các lần đọc tệp diễn ra song song.
  • Duy trì trật tự thông qua việc sử dụng bản đồ để ánh xạ tên tệp cho những lời hứa sẽ chờ đợi.
  • Chờ từng lời hứa theo thứ tự được xác định bởi mảng.

Với giải pháp này, tệp đầu tiên sẽ được hiển thị ngay khi có sẵn mà không cần phải đợi những tệp khác sẵn có trước.

Nó cũng sẽ tải tất cả các tệp cùng một lúc thay vì phải đợi tệp đầu tiên kết thúc trước khi có thể bắt đầu đọc tệp thứ hai.

Điểm rút ra duy nhất của điều này và phiên bản gốc là nếu nhiều lần đọc được bắt đầu cùng một lúc thì việc xử lý lỗi sẽ khó khăn hơn do có nhiều lỗi hơn có thể xảy ra cùng một lúc.

Với các phiên bản đọc một tệp tại một thời điểm thì sau đó sẽ dừng lại do lỗi mà không mất thời gian cố gắng đọc thêm bất kỳ tệp nào. Ngay cả với một hệ thống hủy phức tạp, khó có thể tránh khỏi việc nó không thành công ở tệp đầu tiên nhưng đọc hầu hết các tệp khác cũng vậy.

Hiệu suất không phải lúc nào cũng có thể đoán trước được. Trong khi nhiều hệ thống sẽ nhanh hơn với việc đọc tệp song song một số sẽ thích tuần tự hơn. Một số hoạt động năng động và có thể thay đổi khi tải, các tối ưu hóa cung cấp độ trễ không phải lúc nào cũng mang lại thông lượng tốt trong điều kiện tranh cãi gay gắt.

Cũng không có xử lý lỗi trong ví dụ đó. Nếu điều gì đó yêu cầu tất cả chúng phải được hiển thị thành công hoặc không, nó sẽ không làm được điều đó.

Trong thử nghiệm chuyên sâu được khuyến nghị với console.log ở mỗi giai đoạn và giải pháp đọc tệp giả mạo (thay vào đó là độ trễ ngẫu nhiên). Mặc dù nhiều giải pháp dường như hoạt động tương tự trong các trường hợp đơn giản, nhưng tất cả đều có những khác biệt nhỏ cần phải xem xét kỹ lưỡng hơn để rút ra.

Sử dụng mô hình này để giúp phân biệt sự khác biệt giữa các giải pháp:

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

0
Scott Rudiger 2018-06-22 06:55.

Tương tự như của Antonio Val p-iteration, một mô-đun npm thay thế 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();

Ngoài ra, async-afcó một phương thức tĩnh (log / logAF) ghi lại kết quả của các hứa hẹn:

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

Tuy nhiên, ưu điểm chính của thư viện là bạn có thể xâu chuỗi các phương thức không đồng bộ để thực hiện một số việc như:

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

Related questions

MORE COOL STUFF

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett đã bất chấp những lời khuyên hẹn hò điển hình khi cô gặp chồng mình.

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Michael Sheen là một diễn viên phi lợi nhuận nhưng chính xác thì điều đó có nghĩa là gì?

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Ngôi sao của Hallmark Colin Egglesfield chia sẻ về những cuộc gặp gỡ với người hâm mộ ly kỳ tại RomaDrama Live! cộng với chương trình INSPIRE của anh ấy tại đại hội.

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Bạn sẽ phải phủi sạch đầu đĩa Blu-ray hoặc DVD để xem tại sao Northern Exposure trở thành một trong những chương trình nổi tiếng nhất của thập niên 90.

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

8 công dụng tuyệt vời của Baking Soda và Giấm

8 công dụng tuyệt vời của Baking Soda và Giấm

Bạn biết đấy, hai sản phẩm này là nguồn điện để làm sạch, riêng chúng. Nhưng cùng với nhau, chúng có một loạt công dụng hoàn toàn khác.

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Thủy điện rất cần thiết cho lưới điện của Hoa Kỳ, nhưng nó chỉ tạo ra năng lượng khi có nước di chuyển. Bao nhiêu nhà máy thủy điện có thể gặp nguy hiểm khi các hồ và sông cạn kiệt?

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Tóc tỉa từ các tiệm và các khoản quyên góp cá nhân có thể được tái sử dụng như những tấm thảm thấm dầu và giúp bảo vệ môi trường.

Đây là Tổng thống Trump đe dọa 'trợ cấp' xe điện của GM vì đóng cửa nhà máy

Đây là Tổng thống Trump đe dọa 'trợ cấp' xe điện của GM vì đóng cửa nhà máy

Trump và Giám đốc điều hành GM Mary Barra vào năm 2017. Kể từ khi tin tức nổ ra ngày hôm qua rằng General Motors đang cắt giảm việc làm ở Bắc Mỹ để chuẩn bị cho tương lai điện / xe tự hành / khủng khiếp có thể xảy ra của chúng tôi, gần như tất cả mọi người đã tự hỏi: Tổng thống Trump, người đã vận động tranh cử trên một nền tảng sẽ như thế nào của việc mang lại và duy trì việc làm ô tô của Mỹ, phải nói gì về điều này? Bây giờ chúng ta biết.

Bản vá lỗi tiếp theo của Fallout 76 sẽ mang lại một số bản sửa lỗi cần thiết

Bản vá lỗi tiếp theo của Fallout 76 sẽ mang lại một số bản sửa lỗi cần thiết

Bethesda cho biết ngày hôm nay trên Reddit, Fallout 76 sẽ có một bản vá lớn khác vào ngày 4 tháng 12, sẽ bắt đầu cố gắng khắc phục một số vấn đề lớn hơn của trò chơi. Ngoài ra, công ty cho biết họ có kế hoạch giao tiếp nhiều hơn trong tương lai về những gì nhóm Fallout 76 đang làm việc và khi nào các bản cập nhật mới cho trò chơi sẽ ra mắt.

Mạng lưới bệnh viện Công giáo sẽ không cho phép nhân viên phá thai tại phòng khám ngoài giờ làm việc

Mạng lưới bệnh viện Công giáo sẽ không cho phép nhân viên phá thai tại phòng khám ngoài giờ làm việc

Bạn có biết rằng một trong sáu bệnh nhân ở bệnh viện ở Hoa Kỳ được điều trị tại một cơ sở Công giáo? Và bạn có biết rằng quyền sở hữu của Công giáo hạn chế sâu sắc việc thiết lập các thủ tục y tế có thể được thực hiện trong các bệnh viện này, rõ ràng nhất là khi nói đến chăm sóc sức khỏe sinh sản? Bây giờ NPR báo cáo rằng sự gia tăng của các bệnh viện thuộc Công giáo đang gây khó khăn hơn cho các bác sĩ — những người bị cấm bởi các quy định do Hoa Kỳ viết.

Khói từ đám cháy rừng ở California đã đến tận thành phố New York

Khói từ đám cháy rừng ở California đã đến tận thành phố New York

Đường chân trời San Francisco bị che khuất bởi khói và khói mù do cháy rừng phía sau Đảo Alcatraz, Thứ Tư, ngày 14 tháng 11 năm 2018, ở San Francisco.

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Rothschild's luggage got lost, but luckily she has an incredible closet to shop: Sister Paris Hilton's!

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa. Từ Hollywood đến New York và mọi nơi ở giữa, hãy xem các ngôi sao yêu thích của bạn đang làm gì!

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

Các nhà điều tra đang xem xét liệu nhóm và nghi phạm có biết nhau trước vụ tấn công hay không

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Vụ kiện, nêu tên một số học khu, lập luận rằng dự luật "Không nói đồng tính" được ban hành gần đây của Florida "có hiệu quả im lặng và xóa bỏ học sinh và gia đình LGBTQ +"

Đường băng hạ cánh

Đường băng hạ cánh

Cuối hè đầu thu là mùa hoài niệm. Những chiếc đèn đường chiếu ánh sáng của chúng qua những con đường đẫm mưa, và những chiếc lá dưới chân - màu đỏ cam tắt trong bóng chạng vạng - là lời nhắc nhở về những ngày đã qua.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Vào năm 2021, tôi khuyến khích bạn suy nghĩ lại mọi thứ bạn biết về khách hàng mà bạn phục vụ và những câu chuyện bạn kể cho họ. Lùi lại.

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Vào ngày sinh nhật thứ 9 của Felix The Cat, tôi nhớ về một trong những mất mát lớn nhất trong cuộc đời trưởng thành của tôi - Sophie của tôi vào năm 2013. Tôi đã viết bài luận này và chia sẻ nó trên nền tảng này một thời gian ngắn vào năm 2013.

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Tôi ghét từ "tàu đắm". Mọi người cảm thấy thoải mái trong la bàn đạo đức của riêng mình, và khi làm như vậy, họ thấy mình vượt qua sự phán xét.

Language