web-dev-qa-db-fra.com

Utiliser Promises avec fs.readFile dans une boucle

J'essaie de comprendre pourquoi les configurations de promesse ci-dessous ne fonctionnent pas.

(Remarque: j'ai déjà résolu ce problème avec async.map. Mais j'aimerais savoir pourquoi mes tentatives ci-dessous n'ont pas fonctionné.)

Le comportement correct doit être le suivant: bFunc doit s'exécuter autant de fois que nécessaire pour que fs lise tous les fichiers image (bFunc ci-dessous s'exécute deux fois), puis la console cFunc affiche "End".

Merci!

Tentative 1: Il s'exécute et s'arrête à 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");
}

Tentative 2: Dans ce cas, j'ai utilisé une boucle for mais elle s'exécute dans le désordre. Impressions sur la console: 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");
}

Merci pour l'aide à l'avance!

27
David

Donc, chaque fois que vous avez plusieurs opérations asynchrones à coordonner, je veux tout de suite passer aux promesses. De plus, la meilleure façon d'utiliser des promesses pour coordonner un certain nombre d'opérations asynchrones consiste à faire en sorte que chaque opération asynchrone retourne une promesse. L'opération asynchrone de niveau le plus bas que vous affichez est fs.readFile(). Depuis que j'utilise la bibliothèque de promesses Bluebird, elle a une fonction permettant de "promettre" la valeur asynchrone de tout un module.

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

Cela créera de nouvelles méthodes parallèles sur l'objet fs avec un suffixe "Async" qui renverra des promesses au lieu d'utiliser des rappels directs. Donc, il y aura une fs.readFileAsync() qui retournera une promesse. Vous pouvez en savoir plus sur la promisification de Bluebird ici .

Ainsi, vous pouvez maintenant créer une fonction qui obtient une image assez simplement et renvoie une promesse dont la valeur est les données de l’image:

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

Ensuite, dans votre code, il semble que vous souhaitiez faire de bFunc() une fonction qui lit trois de ces images et appelle cFunc() quand elles sont terminées. Vous pouvez faire ça comme ça:

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

Si vous ne voulez pas utiliser Bluebird, vous pouvez créer manuellement une version de promesse de fs.readFile() comme ceci:

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

Ou, dans les versions modernes de node.js, vous pouvez utiliser util.promisify() pour créer une version promisifiée d'une fonction conforme à la convention d'appel asynchrone de node.js:

const util = require('util');
fs.readFileAsync = util.promisify(fs.readFile);

Cependant, vous constaterez rapidement qu’une fois que vous commencez à utiliser des promesses, vous souhaitez les utiliser pour toutes les opérations asynchrones afin de "promettre" beaucoup de choses et d’avoir une bibliothèque ou au moins une fonction générique qui le fera pour vous. économiser beaucoup de temps.


Dans les versions les plus récentes de node.js (version 10.0+), vous pouvez utiliser la version intégrée de la bibliothèque fs qui prend en charge les promesses:

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

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

Votre code devrait ressembler davantage à ceci:

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

Utilisation avec une seule image:

getImageByIdAsync(0).then(imgBuffer => {
    console.log(imgBuffer);
}).catch(err => {
    console.error(err);
});

Utilisation avec plusieurs images:

var images = [1,2,3,4].map(getImageByIdAsync);

Promise.all(images).then(imgBuffers => {
    // all images have loaded
}).catch(err => {
    console.error(err);
});

Pour promisify une fonction signifie prendre une fonction asynchrone avec une sémantique de rappel et en dériver une nouvelle fonction avec une sémantique de promesse.

Cela peut être fait manuellement, comme indiqué ci-dessus, ou - de préférence - automatiquement. Entre autres, la bibliothèque de promesses Bluebird a un assistant pour cela, voir http://bluebirdjs.com/docs/api/promisification.html

22
Tomalak

Node v10 a l'API fs Promises

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

Ou si vous voulez lire plus de fichiers simultanément:

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

vous pouvez aussi utiliser ce module: '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