在循环中使用带有 fs.readFile 的 Promises
Using Promises with fs.readFile in a loop
我正在尝试了解为什么以下承诺设置不起作用。
(注意:我已经用 async.map 解决了这个问题。但我想了解为什么我的以下尝试不起作用。)
正确的行为应该是:bFunc 应该 运行 多次 fs 读取所有图像文件(bFunc 低于 运行s 两次)然后 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");
}
提前感谢您的帮助!
您的代码应该更像这样:
// 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);
});
对promisify一个函数意味着采用一个具有回调语义的异步函数,并从中派生出一个具有promise语义的新函数。
它可以手动完成,如上所示,或者 - 最好 - 自动完成。其中,Bluebird promise 库有一个帮助程序,请参阅 http://bluebirdjs.com/docs/api/promisification.html
因此,只要您有多个异步操作需要以某种方式进行协调,我就会立即想到 promises。而且,使用 promise 协调多个异步操作的最佳方式是让每个异步操作 return 成为一个 promise。您显示的最低级别异步操作是 fs.readFile()
。因为我使用了 Bluebird promise 库,所以它有一个函数 "promisifying" 整个模块的异步函数。
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
这将在带有 "Async" 后缀的 fs
对象上创建新的并行方法,return 承诺而不是使用直接回调。因此,将有一个 fs.readFileAsync()
return 的承诺。您可以阅读有关 Bluebird 的承诺的更多信息 here。
所以,现在您可以创建一个函数来相当简单地获取图像,并且 return 是一个承诺,其值是来自图像的数据:
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()
的 promise 版本:
// 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);
不过,您很快就会发现,一旦您开始使用 promises,您就想将它们用于所有异步操作,因此您会 "promisifying" 很多东西,并且拥有一个库或至少一个通用函数这将为您节省大量时间。
在 node.js 的更新版本(10.0+ 版本)中,您可以使用支持 promises 的 fs
库的 built-in 版本:
const fsp = require('fs').promises;
fsp.readFile("someFile").then(data => {
console.log(data);
});
节点 v10 有 fs Promise API
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)
你也可以使用这个模块:
'fs-readfile-promise'
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)
})
如果您使用 .mjs
ECMAScript 模块 import
语法,那么这里是读取文件的代码,基于 this GitHub gist comment reply:
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');
我曾经写过一个在循环中使用 promises 的库。它被称为for-async
。它很小,但是很难把所有的细节都做好,所以你可以看看这个来寻找灵感。
这主要按顺序处理运行 promise-returning 函数,等待promise 解决后再继续下一项和处理错误。这与 Promise.map
不同,因为该函数并行运行 promise-returning 函数,这可能是也可能不是您想要的。
// example: looping over promise-returning functions with forAsync
forAsync(
['a.txt', 'b.txt', 'c.txt'],
readFile
)
// worker function that returns a promise
function readFile(name, idx) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.info('read file ' + idx + ': ' + name)
resolve()
}, 1000)
})
}
// forAsync
function forAsync(arr, work) {
function loop(arr, i) {
return new Promise(function (resolve, reject) {
if (i >= arr.length) {
resolve();
} else try {
Promise.resolve(work(arr[i], i)).then(function () {
return resolve(loop(arr, i + 1));
}).catch(reject);
} catch (error) {
reject(error);
}
});
}
return loop(arr, 0);
}
我正在尝试了解为什么以下承诺设置不起作用。
(注意:我已经用 async.map 解决了这个问题。但我想了解为什么我的以下尝试不起作用。)
正确的行为应该是:bFunc 应该 运行 多次 fs 读取所有图像文件(bFunc 低于 运行s 两次)然后 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");
}
提前感谢您的帮助!
您的代码应该更像这样:
// 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);
});
对promisify一个函数意味着采用一个具有回调语义的异步函数,并从中派生出一个具有promise语义的新函数。
它可以手动完成,如上所示,或者 - 最好 - 自动完成。其中,Bluebird promise 库有一个帮助程序,请参阅 http://bluebirdjs.com/docs/api/promisification.html
因此,只要您有多个异步操作需要以某种方式进行协调,我就会立即想到 promises。而且,使用 promise 协调多个异步操作的最佳方式是让每个异步操作 return 成为一个 promise。您显示的最低级别异步操作是 fs.readFile()
。因为我使用了 Bluebird promise 库,所以它有一个函数 "promisifying" 整个模块的异步函数。
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
这将在带有 "Async" 后缀的 fs
对象上创建新的并行方法,return 承诺而不是使用直接回调。因此,将有一个 fs.readFileAsync()
return 的承诺。您可以阅读有关 Bluebird 的承诺的更多信息 here。
所以,现在您可以创建一个函数来相当简单地获取图像,并且 return 是一个承诺,其值是来自图像的数据:
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()
的 promise 版本:
// 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);
不过,您很快就会发现,一旦您开始使用 promises,您就想将它们用于所有异步操作,因此您会 "promisifying" 很多东西,并且拥有一个库或至少一个通用函数这将为您节省大量时间。
在 node.js 的更新版本(10.0+ 版本)中,您可以使用支持 promises 的 fs
库的 built-in 版本:
const fsp = require('fs').promises;
fsp.readFile("someFile").then(data => {
console.log(data);
});
节点 v10 有 fs Promise API
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)
你也可以使用这个模块: 'fs-readfile-promise'
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)
})
如果您使用 .mjs
ECMAScript 模块 import
语法,那么这里是读取文件的代码,基于 this GitHub gist comment reply:
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');
我曾经写过一个在循环中使用 promises 的库。它被称为for-async
。它很小,但是很难把所有的细节都做好,所以你可以看看这个来寻找灵感。
这主要按顺序处理运行 promise-returning 函数,等待promise 解决后再继续下一项和处理错误。这与 Promise.map
不同,因为该函数并行运行 promise-returning 函数,这可能是也可能不是您想要的。
// example: looping over promise-returning functions with forAsync
forAsync(
['a.txt', 'b.txt', 'c.txt'],
readFile
)
// worker function that returns a promise
function readFile(name, idx) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.info('read file ' + idx + ': ' + name)
resolve()
}, 1000)
})
}
// forAsync
function forAsync(arr, work) {
function loop(arr, i) {
return new Promise(function (resolve, reject) {
if (i >= arr.length) {
resolve();
} else try {
Promise.resolve(work(arr[i], i)).then(function () {
return resolve(loop(arr, i + 1));
}).catch(reject);
} catch (error) {
reject(error);
}
});
}
return loop(arr, 0);
}