web-dev-qa-db-ja.com

ループでのfs.readFileでのPromiseの使用

私は、以下の約束のセットアップが機能しない理由を理解しようとしています。

(注:この問題は既にasync.mapで解決しています。しかし、以下の試みがうまくいかなかった理由を知りたいです。)

正しい動作は次のとおりです。bFuncは、すべてのイメージファイルを読み取るために必要な回数実行し(以下のbFuncが2回実行されます)、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");
}

事前に助けてくれてありがとう!

27
David

したがって、何らかの方法で調整する複数の非同期操作がある場合は、すぐに約束に行きたいと思います。そして、promiseを使用して多くの非同期操作を調整する最良の方法は、各非同期操作がpromiseを返すようにすることです。表示する最低レベルの非同期操作はfs.readFile()です。私はBluebirdのpromiseライブラリを使用しているため、モジュール全体の非同期機能を「約束」する機能があります。

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

これにより、fsオブジェクトに新しい非同期メソッドが作成され、サフィックスが「非同期」になり、ストレートコールバックを使用する代わりにプロミスが返されます。したがって、promiseを返すfs.readFileAsync()があります。 Bluebirdの約束について詳しく読むことができます こちら

したがって、画像をかなり簡単に取得し、その値が画像のデータであるプロミスを返す関数を作成できます。

_ function getImage(index) {
     var imgPath = __dirname + "/image1" + index + ".png";
     return fs.readFileAsync(imgPath);
 }
_

次に、コードで、bFunc()をこれらのイメージのうち3つを読み取り、完了時に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);
});
_
63
jfriend00

コードは次のようになります。

// 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 を参照してください

22
Tomalak

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)
22
Dmitry Yudakov

このモジュールを使用することもできます: '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)
})
0
Changyuan Chen