Использование обещаний с fs.функцию ReadFile в цикле

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

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

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

спасибо!

Попытка 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-loop, но он выполняется не по порядку. Отпечатки консоли: End, bfunc done, bfunc done

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

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

3 ответов


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

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

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

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

 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() сделать версию promisified функции, которая следует за узел.соглашение о вызове JS async:

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

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


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

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

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

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


узел v10 имеет экспериментальные fs обещает API

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#fs_fs_promises_api