Использование 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, но он не работает. Консольные отпечатки: 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");
}
Спасибо за помощь заранее!
Ответы
Ответ 1
Поэтому, когда у вас есть несколько асинхронных операций для какой-либо координации, я сразу же хочу перейти к обещаниям. И лучший способ использовать обещания для координации ряда асинхронных операций - заставить каждую асинхронную операцию возвращать обещание. Асинхронная операция самого низкого уровня, которую вы показываете, - 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);
Тем не менее, вы быстро обнаружите, что как только вы начнете использовать обещания, вы захотите использовать их для всех асинхронных операций, так что вы будете "обещать" много вещей и иметь библиотеку или, по крайней мере, универсальную функцию, которая будет делать это. потому что вы сэкономите много времени.
В еще более новых версиях node.js (версия 10. 0+) вы можете использовать встроенную версию библиотеки fs
, которая поддерживает обещания:
const fsp = require('fs').promises;
fsp.readFile("someFile").then(data => {
console.log(data);
});
Ответ 2
Ваш код должен выглядеть примерно так:
// promisify fs.readFile()
fs.readFileAsync = function (filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, (err, buffer) => {
if (err) reject(err); else resolve(buffer);
});
});
};
const IMG_PATH = "foo";
// utility function
function getImageByIdAsync(i) {
return fs.readFileAsync(IMG_PATH + "/image1" + i + ".png");
}
Использование с одним изображением:
getImageByIdAsync(0).then(imgBuffer => {
console.log(imgBuffer);
}).catch(err => {
console.error(err);
});
Использование с несколькими изображениями:
var images = [1,2,3,4].map(getImageByIdAsync);
Promise.all(images).then(imgBuffers => {
// all images have loaded
}).catch(err => {
console.error(err);
});
Обещать функцию - значит взять асинхронную функцию с семантикой обратного вызова и извлечь из нее новую функцию с семантикой обещания.
Это можно сделать вручную, как показано выше, или - предпочтительно - автоматически. Среди прочего, библиотека обещаний Bluebird имеет помощника для этого, см. http://bluebirdjs.com/docs/api/promisification.html
Ответ 3
Узел v10 имеет API 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'])
.then(res => console.log('all read', res))
.catch(console.log)
https://nodejs.org/api/fs.html#fs_fs_promises_api
Или, если вы хотите прочитать больше файлов одновременно:
const func = filenames => {
return Promise.all(
filenames.map(f => fsPromises.readFile(f))
)
}
func(['./a','./b'])
.then(res => console.log('all read', res))
.catch(console.log)
Ответ 4
Вы также можете использовать этот модуль: 'fs-readfile-обещание'
var readFile = require('fs-readfile-promise');
readFile(__dirname + '/file1.txt','utf-8').then(function (data){
console.log("file name:", data)
return readFile(__dirname +'/'+data, 'utf-8')
}).then(function (data1){
console.log('Content data:', data1)
}).catch( function (err){
console.log(err)
})