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

新手上路,请多包涵

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

(注意:我已经用 async.map 解决了这个问题。但我想了解为什么我在下面的尝试没有奏效。)

正确的行为应该是: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 循环,但它执行无序。控制台打印:结束,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");
}

我在这里先向您的帮助表示感谢!

原文由 David 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 632
2 个回答

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

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

这将在 fs 对象上创建新的并行方法,该对象带有“异步”后缀,返回承诺而不是使用直接回调。所以,会有一个 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);

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


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

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

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

原文由 jfriend00 发布,翻译遵循 CC BY-SA 4.0 许可协议

我曾经写过一个在循环中使用 Promise 的库。它被称为 for-async 。它非常小,但要正确处理所有细节很困难,所以你可以看看这个以获得灵感。

这主要负责按顺序运行返回承诺的函数,等待承诺解决后再进行下一项,并处理错误。这与 Promise.map 不同,因为该函数并行运行承诺返回函数,这可能是也可能不是您想要的。

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

原文由 Stijn de Witt 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题