在循环中使用带有 fs.readFile 的 Promises

Posted

技术标签:

【中文标题】在循环中使用带有 fs.readFile 的 Promises【英文标题】:Using Promises with fs.readFile in a loop 【发布时间】:2016-04-10 06:12:43 【问题描述】:

我试图了解为什么以下承诺设置不起作用。

(注意:我已经用 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");

提前感谢您的帮助!

【问题讨论】:

在尝试 1 中,bFunc 的错误处理程序在哪里?如果抛出错误,您将永远无法使用当前代码了解它。 这段代码的目标是什么?请用文字描述您试图解决的问题,而不是仅仅向我们展示无法满足您的需求的代码。您的代码有很多问题,所以我宁愿从了解要解决的问题开始,也不愿在不知道实际最终目标的情况下尝试修改代码中的所有错误。 @nils 它不会在 bFunc 上抛出错误!但我想我应该在链的末端放一个陷阱。 @jfriend00 我去掉了代码的所有细节,只留下结构来简化它。问题是一样的,但如果有帮助的话,我使用微软的牛津项目进行面部检测,然后在每个“.then”中我用眼镜、帽子等来增加照片。我可以与你分享源代码。谢谢。 好吧,从我们的角度考虑。您给了我们两个不起作用的代码块,并询问我们如何修复它们。但是,您永远不会真正描述他们应该做什么(正确的行为是什么)。我们是否应该从故障代码中猜测正确的行为是什么?请描述您希望从任一代码块中获得的所需和适当的行为。 【参考方案1】:

您的代码应该看起来更像这样:

// 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 一个函数意味着接受一个具有回调语义的异步函数,并从中派生一个具有承诺语义的新函数。

它可以手动完成,如上所示,或者 - 最好 - 自动完成。其中,Bluebird Promise 库为此提供了帮助,请参阅 http://bluebirdjs.com/docs/api/promisification.html

【讨论】:

我只是好奇你为什么把函数 fs.readFile() 放在 try catch 中?回调可以处理错误吧? 不错!谢谢。 `return function () return new Promise(function(resolve, reject) try fs.readFile ` 对我有用 gulp4【参考方案2】:

所以,每当您有多个异步操作以某种方式进行协调时,我都会立即使用 Promise。而且,使用 Promise 协调多个异步操作的最佳方式是让每个异步操作都返回一个 Promise。您显示的最低级别的异步操作是fs.readFile()。由于我使用 Bluebird Promise 库,它具有“承诺”整个模块的异步函数价值的功能。

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

这将在 fs 对象上创建新的并行方法,并带有“Async”后缀,返回 Promise 而不是使用直接回调。因此,将有一个 fs.readFileAsync() 返回一个承诺。你可以阅读更多关于 Bluebird 的承诺here。

所以,现在你可以创建一个函数来相当简单地获取图像并返回一个承诺,其值是图像中的数据:

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

不过,您很快就会发现,一旦开始使用 Promise,您就希望将它们用于所有异步操作,因此您将“承诺”很多事情,并拥有一个库或至少一个通用函数来做到这一点因为你会节省很多时间。


在更新版本的 node.js(10.0+ 版本)中,您可以使用支持 Promise 的 fs 库的内置版本:

const fsp = require('fs').promises;

fsp.readFile("someFile").then(data => 
    console.log(data);
);

【讨论】:

请注意,您现在必须使用Promise.promisifyAll 而不是Promise.promisify 注意node 8自带promisify函数:const promisify = require("util"); const fs = require("fs"); const readFile = promisify(fs.readFile); @Daniel - 在较新版本的 node.js 中使用 util.promisify() 作为选项已经在我的回答中。 添加了有关 node.js v10+ 中内置 fs 承诺支持的信息。 @MzA - 现在的标准方法是使用fs.promises.readFile 的最后一个代码块。我会考虑重新组织答案。【参考方案3】:

Node v10 具有 fs Promises 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)

【讨论】:

注意:截至2019-06-28,该功能已经稳定了一段时间。 好消息!尽管您的示例没有什么意义,因为文件是同步读取的,对吗?在触发下一个承诺之前,每个承诺都会等待解决。 @estani 这样它就在一个循环中;)但这是另一个并行执行的例子【参考方案4】:

你也可以使用这个模块: '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)
)

【讨论】:

【参考方案5】:

如果您使用.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);

这是基于this answer 处理多个文件的代码:

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

【讨论】:

以上是关于在循环中使用带有 fs.readFile 的 Promises的主要内容,如果未能解决你的问题,请参考以下文章

使用 fs 显示带有图像的 html

fs.readFile 返回字符串,但每个字符之间有空格并添加字符

如何将使用 fs.readFileSync() 的 Node.js 代码重构为使用 fs.readFile()?

fs.readFile 无法在 nodejs 中使用 ffmpeg 读取缩略图生成的文件

使用 require vs fs.readFile 读取 json 文件内容

node.js 中 fs.createReadStream 与 fs.readFile 的优缺点是啥?