Использование Promises с fs.readFile в цикле

Я пытаюсь понять, почему нижеприведенные настройки не работают.

(Примечание: я уже решил эту проблему с помощью async.map. Но я хотел бы узнать, почему мои попытки ниже не сработали.)

Правильное поведение должно быть таким: bFunc должен запускаться столько раз, сколько необходимо, чтобы fs прочитал все файлы изображений (bFunc ниже запускается дважды), а затем консоль cFunc выдает "End".

Спасибо!

Попытка 1: он запускается и останавливается на cFunc().

var fs = require('fs');

bFunc(0)
.then(function(){ cFunc() }) //cFunc() doesn't run

function bFunc(i){
    return new Promise(function(resolve,reject){

        var imgPath = __dirname + "/image1" + i + ".png";

        fs.readFile(imgPath, function(err, imagebuffer){

            if (err) throw err;
            console.log(i)

            if (i<1) {
                i++;
                return bFunc(i);
            } else {
                resolve();
            };

        });

    })
}

function cFunc(){
    console.log("End");
}

Попытка 2: в этом случае я использовал цикл for, но он выполняется не по порядку. Печать на консоли: конец, bFunc сделано, bFunc сделано

var fs = require('fs');

bFunc()
        .then(function(){ cFunc() })

function bFunc(){
    return new Promise(function(resolve,reject){

        function read(filepath) {
            fs.readFile(filepath, function(err, imagebuffer){
                if (err) throw err;
                console.log("bFunc done")
            });
        }

        for (var i=0; i<2; i++){
            var imgPath = __dirname + "/image1" + i + ".png";
            read(imgPath);
        };

        resolve()
    });
}


function cFunc(){
    console.log("End");
}

Спасибо за помощь в продвижении!

7 ответов

Решение

Поэтому, когда у вас есть несколько асинхронных операций для какой-либо координации, я немедленно хочу перейти к обещаниям. И лучший способ использовать обещания для координации ряда асинхронных операций - заставить каждую асинхронную операцию возвращать обещание. Асинхронная операция самого низкого уровня, которую вы показываете, fs.readFile(), Поскольку я использую библиотеку обещаний Bluebird, в ней есть функция для "обещания" асинхронных функций всего модуля.

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

Это создаст новые параллельные методы на fs объект с суффиксом "Async", который возвращает обещания вместо использования прямых обратных вызовов. Итак, будет fs.readFileAsync() это возвращает обещание. Вы можете прочитать больше об обещании Bluebird здесь.

Итак, теперь вы можете сделать функцию, которая получает изображение довольно просто и возвращает обещание, значением которого являются данные из изображения:

 function getImage(index) {
     var imgPath = __dirname + "/image1" + index + ".png";
     return fs.readFileAsync(imgPath);
 }

Затем, в вашем коде, похоже, что вы хотите сделать bFunc() быть функцией, которая читает три из этих изображений и вызовов cFunc() когда они будут сделаны. Вы можете сделать это так:

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

 function getImage(index) {
     var imgPath = __dirname + "/image1" + index + ".png";
     return fs.readFileAsync(imgPath);
 }

 function getAllImages() {
    var promises = [];
    // load all images in parallel
    for (var i = 0; i <= 2; i++) {
        promises.push(getImage(i));
    }
    // return promise that is resolved when all images are done loading
    return Promise.all(promises);
 }

 getAllImages().then(function(imageArray) {
    // you have an array of image data in imageArray
 }, function(err) {
    // an error occurred
 });

Если вы не хотите использовать Bluebird, вы можете вручную сделать обещанную версию fs.readFile() как это:

// make promise version of fs.readFile()
fs.readFileAsync = function(filename) {
    return new Promise(function(resolve, reject) {
        fs.readFile(filename, function(err, data){
            if (err) 
                reject(err); 
            else 
                resolve(data);
        });
    });
};

Или, в современных версиях node.js, вы можете использовать util.promisify() сделать обещанную версию функции, которая следует соглашению асинхронных вызовов node.js:

const util = require('util');
fs.readFileAsync = util.promisify(fs.readFile);

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

Узел v10 имеет экспериментальный интерфейс fs Promises

const fsPromises = require('fs').promises

const func = async filenames => {

  for(let fn of filenames) {
    let data = await fsPromises.readFile(fn)
  }

}

func(['file1','file2'])

https://nodejs.org/api/fs.html

Ваш код должен выглядеть примерно так:

var fs = require('fs');
var __dirname = "foo";

// promisify fs.readFile()
fs.readFileAsync = function (filename) {
    return new Promise(function (resolve, reject) {
        try {
            fs.readFile(filename, function(err, buffer){
                if (err) reject(err); else resolve(buffer);
            });
        } catch (err) {
            reject(err);
        }
    });
};

// utility function
function getImageAsync(i) {
    return fs.readFileAsync(__dirname + "/image1" + i + ".png");
}

Использование с одним изображением:

getImageAsync(0).then(function (imgBuffer){
    console.log(imgBuffer);
}).catch(function (err) {
    console.error(err);
});

Использование с несколькими изображениями:

var images = [1,2,3,4].map(getImageAsync);
Promise.all(images).then(function (imgBuffers) {
    // all images have loaded
}).catch(function (err) {
    console.error(err);
});

Обещать функцию означает взять асинхронную функцию с семантикой обратного вызова и извлечь из нее новую функцию с семантикой обещания.

Это можно сделать вручную, как показано выше, или - предпочтительно - автоматически. Среди прочего, в библиотеке обещаний Bluebird есть помощник, см. http://bluebirdjs.com/docs/api/promisification.html

Если вы используете .mjsМодули ECMAScript importсинтаксис, то вот код, который читает файл, на основе этого ответа GitHub gist comment:

      import { promises as fs } from 'fs';
let json = await fs.readFile('./package.json', 'utf-8');
console.log(json);

Вот код, который делает несколько файлов на основе этого ответа:

      import { promises as fs } from 'fs';

const sequentially = async filenames => {
    for(let fn of filenames) {
        let json = await fs.readFile(fn, 'utf-8');
        console.log(json);
    }
}

const parallel = filenames => {
    return Promise.all(
      filenames.map(fn => fs.readFile(fn, 'utf-8'))
    )
  }

const fns = ['package.json', 'package-lock.json'];
await sequentially(fns);
await parallel(fns);
console.log('all read');

более современный

      import { readFile, writeFile } from 'fs/promises';

Однажды я написал библиотеку для использования промисов в цикле. Это называетсяfor-async. Он довольно маленький, но правильно понять все детали сложно, поэтому, возможно, вы можете взглянуть на него для вдохновения.

В основном это заботится о последовательном запуске функций, возвращающих промис, ожидании разрешения промиса перед переходом к следующему элементу и обработке ошибок. Это отличается от Promise.mapпотому что эта функция запускает функции, возвращающие обещания, параллельно, что может быть, а может и не быть тем, что вам нужно.

Вы также можете использовать этот модуль: 'fs-readfile-обещание'

var readFile = require('fs-readfile-promise');
readFile(__dirname + '/file1.txt','utf-8').then(function (data){
    console.log("file's name:", data)
    return readFile(__dirname +'/'+data, 'utf-8')
}).then(function (data1){
    console.log('Content data:', data1)
}).catch( function (err){
    console.log(err)
})

Другие вопросы по тегам