Использование обещаний с 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'])