Using promises with download module

2019-02-25 01:26发布

问题:

I am using bluebird for promises.

I am trying to promisify the download module.

Here is my implementation:

Promise  = require('bluebird'),
download = require('download');

var methodNameToPromisify = ["download"];

function EventEmitterPromisifier(originalMethod) {
    // return a function
    return function promisified() {
        var args = [].slice.call(arguments);
        // Needed so that the original method can be called with the correct receiver
        var self = this;
        // which returns a promise
        return new Promise(function(resolve, reject) {
            // We call the originalMethod here because if it throws,
            // it will reject the returned promise with the thrown error
            var emitter = originalMethod.apply(self, args);

            emitter
                .on("response", function(data) {
                    resolve(data);
                })
                .on("data ", function(data) {
                    resolve(data);
                })
                .on("error", function(err) {
                    reject(err);
                })
                .on("close", function() {
                    resolve();
                });
        });
    };
};
download = { download: download };
Promise.promisifyAll(download, {
    filter: function(name) {
        return methodNameToPromisify.indexOf(name) > -1;
    },
    promisifier: EventEmitterPromisifier
});

Then using it:

return download.downloadAsync(fileURL, copyTo, {});

My problem is that it doesn't download all of the files (I have a list sent to this function), what am I doing wrong?

回答1:

An emitter does emit multiple data events, one for every chunk it receives. However, a represents only one future value, in your case you want that to be the complete response.

resolve is supposed to be called only once, to fulfill the promise with the passed value, which is then settled. Further calls will have no effect - and that's why you get only the first parts of your list.

Instead, you will need to accumulate all the data, and when the stream ends you can fulfill the promise with all of it.

var Promise  = require('bluebird'),
    download = require('download'),
    Buffer   = require('buffer'); // should be global anyway

exports = {
    downloadAsync: function promisifiedDownload() {
        var args = arguments, self = this;

        return new Promise(function(resolve, reject) {
            // We call the originalMethod here because if it throws,
            // it will reject the returned promise with the thrown error
            var emitter = download.apply(self, args);
            var buffers = [];

            emitter.on("data", function(data) {
                buffers.push(data);
            }).on("error", function(err) {
                reject(err);
            }).on("close", function() {
                resolve(Buffer.concat(buffers));
            });
        });
    };
};

Notice it's quite nonsensical to use promisifyAll when you only want to promisify a single method. I've omitted it for simplicity

You might also listen for the incoming response object, and attach the data listener directly to it. You can then use the end event instead of close.