web-dev-qa-db-fra.com

Utiliser des promesses avec des flux dans node.js

J'ai refactored un utilitaire simple pour utiliser les promesses. Il récupère un pdf sur le Web et l'enregistre sur disque. Il convient ensuite d'ouvrir le fichier dans un visualiseur pdf une fois enregistré sur disque. Le fichier apparaît sur le disque et est valide. La commande Shell ouvre l’application OSX Preview, mais une boîte de dialogue apparaît pour indiquer que le fichier est vide.

Quel est le meilleur moyen d’exécuter la fonction Shell une fois que le flux de fichiers a été écrit sur le disque?

// download a pdf and save to disk
// open pdf in osx preview for example
download_pdf()
  .then(function(path) {
    Shell.exec('open ' + path).code !== 0);
  });

function download_pdf() {
  const path = '/local/some.pdf';
  const url = 'http://somewebsite/some.pdf';
  const stream = request(url);
  const write = stream.pipe(fs.createWriteStream(path))
  return streamToPromise(stream);
}

function streamToPromise(stream) {
  return new Promise(function(resolve, reject) {
    // resolve with location of saved file
    stream.on("end", resolve(stream.dests[0].path));
    stream.on("error", reject);
  })
}
6
pompon

Dans cette ligne

stream.on("end", resolve(stream.dests[0].path));

vous exécutez resolve immédiatement, et le result de l'appel resolve (qui sera indéfini, car c'est ce que resolve renvoie) est utilisé comme argument pour stream.on - pas ce que vous voulez du tout, d'accord.

Le second argument de .on doit être un function, plutôt que le résultat de l'appel d'une fonction

Par conséquent, le code doit être

stream.on("end", () => resolve(stream.dests[0].path));

ou, si vous êtes vieille école:

stream.on("end", function () { resolve(stream.dests[0].path); });

une autre vieille école serait quelque chose comme

stream.on("end", resolve.bind(null, stream.dests[0].path)); </ s>

Non, ne fais pas ça: p voir les commentaires

10
Jaromanda X

Après plusieurs essais, j'ai trouvé une solution qui fonctionne bien tout le temps. Voir les commentaires de JSDoc pour plus d'informations.

/**
 * Streams input to output and resolves only after stream has successfully ended.
 * Closes the output stream in success and error cases.
 * @param input {stream.Readable} Read from
 * @param output {stream.Writable} Write to
 * @return Promise Resolves only after the output stream is "end"ed or "finish"ed.
 */
function promisifiedPipe(input, output) {
    let ended = false;
    function end() {
        if (!ended) {
            ended = true;
            output.close && output.close();
            input.close && input.close();
            return true;
        }
    }

    return new Promise((resolve, reject) => {
        input.pipe(output);
        input.on('error', errorEnding);

        function niceEnding() {
            if (end()) resolve();
        }

        function errorEnding(error) {
            if (end()) reject(error);
        }

        output.on('finish', niceEnding);
        output.on('end', niceEnding);
        output.on('error', errorEnding);
    });
};

Exemple d'utilisation:

function downloadFile(req, res, next) {
  promisifiedPipe(fs.createReadStream(req.params.file), res).catch(next);
}
4
Vasyl Boroviak

L’autre solution peut ressembler à ceci:

const streamAsPromise = (readable) => {
  const result = []
  const w = new Writable({
    write(chunk, encoding, callback) {·
      result.Push(chunk)
      callback()
    }
  })
  readable.pipe(w)
  return new Promise((resolve, reject) => {
    w.on('finish', resolve)
    w.on('error', reject)
  }).then(() => result.join(''))
}

et vous pouvez l'utiliser comme:

streamAsPromise(fs.createReadStream('secrets')).then(() => console.log(res))
1
kharandziuk