Javascript 如何使用 Node.js 下载文件(不使用第三方库)?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11944932/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 07:46:41  来源:igfitidea点击:

How to download a file with Node.js (without using third-party libraries)?

javascriptnode.jsexpressdownloadfs

提问by greepow

How do I download a file with Node.js without using third-party libraries?

如何在不使用第三方库的情况下使用Node.js 下载文件?

I don't need anything special. I only want to download a file from a given URL, and then save it to a given directory.

我不需要什么特别的。我只想从给定的 URL 下载文件,然后将其保存到给定的目录。

回答by Michelle Tilley

You can create an HTTP GETrequest and pipe its responseinto a writable file stream:

您可以创建一个 HTTPGET请求并将其response通过管道传输到一个可写的文件流中:

const http = require('http');
const fs = require('fs');

const file = fs.createWriteStream("file.jpg");
const request = http.get("http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg", function(response) {
  response.pipe(file);
});

If you want to support gathering information on the command line--like specifying a target file or directory, or URL--check out something like Commander.

如果你想支持在命令行上收集信息——比如指定目标文件或目录,或者 URL——检查像Commander这样的东西。

回答by Vince Yuan

Don't forget to handle errors! The following code is based on Augusto Roman's answer.

不要忘记处理错误!以下代码基于 Augusto Roman 的回答。

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  }).on('error', function(err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    if (cb) cb(err.message);
  });
};

回答by gfxmonk

As Michelle Tilley said, but with the appropriate control flow:

正如 Michelle Tilley 所说,但使用适当的控制流程:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);
    });
  });
}

Without waiting for the finishevent, naive scripts may end up with an incomplete file.

如果不等待finish事件,幼稚的脚本可能会以不完整的文件结束。

Edit:Thanks to @Augusto Roman for pointing out that cbshould be passed to file.close, not called explicitly.

编辑:感谢@Augusto Roman 指出cb应该传递给file.close,而不是显式调用。

回答by Buzut

Speaking of handling errors, it's even better listening to request errors too. I'd even validate by checking response code. Here it's considered success only for 200 response code, but other codes might be good.

说到处理错误,听请求错误甚至更好。我什至会通过检查响应代码来验证。这里仅对 200 个响应代码认为成功,但其他代码可能很好。

const fs = require('fs');
const http = require('http');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);

    const request = http.get(url, (response) => {
        // check if response is success
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        response.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request error too
    request.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result) 
        return cb(err.message);
    });
};

Despite the relative simplicity of this code, I would advise to use the request moduleas it handles many more protocols (hello HTTPS!) which aren't natively supported by http.

尽管这段代码相对简单,我还是建议使用request 模块,因为它可以处理更多的协议(你好 HTTPS!),而http.

That would be done like so:

这样做会像这样:

const fs = require('fs');
const request = require('request');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);
    const sendReq = request.get(url);

    // verify response code
    sendReq.on('response', (response) => {
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        sendReq.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request errors
    sendReq.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        return cb(err.message);
    });
};

回答by Augusto Roman

gfxmonk's answer has a very tight data race between the callback and the file.close()completing. file.close()actually takes a callback that is called when the close has completed. Otherwise, immediate uses of the file may fail (very rarely!).

gfxmonk 的回答在回调和file.close()完成之间有一个非常紧张的数据竞争。 file.close()实际上需要一个在关闭完成时调用的回调。否则,文件的立即使用可能会失败(很少见!)。

A complete solution is:

一个完整的解决方案是:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  });
}

Without waiting for the finish event, naive scripts may end up with an incomplete file. Without scheduling the cbcallback via close, you may get a race between accessing the file and the file actually being ready.

无需等待完成事件,幼稚的脚本可能会以不完整的文件结束。如果不cb通过 close安排回调,您可能会在访问文件和文件实际准备好之间发生竞争。

回答by Bjarke Pjedsted

Maybe node.js has changed, but it seems there are some problems with the other solutions (using node v8.1.2):

也许 node.js 发生了变化,但其他解决方案似乎存在一些问题(使用 node v8.1.2):

  1. You don't need to call file.close()in the finishevent. Per default the fs.createWriteStreamis set to autoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()should be called on error. Maybe this is not needed when the file is deleted (unlink()), but normally it is: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Temp file is not deleted on statusCode !== 200
  4. fs.unlink()without a callback is deprecated (outputs warning)
  5. If destfile exists; it is overridden
  1. 您不需要file.close()finish事件中调用。默认fs.createWriteStream设置为自动关闭:https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()应该在错误时调用。删除文件时可能不需要这样做(unlink()),但通常是:https: //nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. 临时文件未删除 statusCode !== 200
  4. fs.unlink()不推荐使用没有回调(输出警告)
  5. 如果dest文件存在;它被覆盖

Below is a modified solution (using ES6 and promises) which handles these problems.

下面是处理这些问题的修改后的解决方案(使用 ES6 和 promises)。

const http = require("http");
const fs = require("fs");

function download(url, dest) {
    return new Promise((resolve, reject) => {
        const file = fs.createWriteStream(dest, { flags: "wx" });

        const request = http.get(url, response => {
            if (response.statusCode === 200) {
                response.pipe(file);
            } else {
                file.close();
                fs.unlink(dest, () => {}); // Delete temp file
                reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
            }
        });

        request.on("error", err => {
            file.close();
            fs.unlink(dest, () => {}); // Delete temp file
            reject(err.message);
        });

        file.on("finish", () => {
            resolve();
        });

        file.on("error", err => {
            file.close();

            if (err.code === "EEXIST") {
                reject("File already exists");
            } else {
                fs.unlink(dest, () => {}); // Delete temp file
                reject(err.message);
            }
        });
    });
}

回答by A-312

Solution with timeout, prevent memory leak :

解决超时,防止内存泄漏:

The following code is based on Brandon Tilley's answer :

以下代码基于 Brandon Tilley 的回答:

var http = require('http'),
    fs = require('fs');

var request = http.get("http://example12345.com/yourfile.html", function(response) {
    if (response.statusCode === 200) {
        var file = fs.createWriteStream("copy.html");
        response.pipe(file);
    }
    // Add timeout.
    request.setTimeout(12000, function () {
        request.abort();
    });
});

Don't make file when you get an error, and prefere to use timeout to close your request after X secondes.

出现错误时不要创建文件,并且更喜欢在 X 秒后使用超时来关闭您的请求。

回答by mido

for those who came in search of es6-style promise based way, I guess it would be something like:

对于那些寻找基于 es6 风格的 Promise 方式的人,我想它会是这样的:

var http = require('http');
var fs = require('fs');

function pDownload(url, dest){
  var file = fs.createWriteStream(dest);
  return new Promise((resolve, reject) => {
    var responseSent = false; // flag to make sure that response is sent only once.
    http.get(url, response => {
      response.pipe(file);
      file.on('finish', () =>{
        file.close(() => {
          if(responseSent)  return;
          responseSent = true;
          resolve();
        });
      });
    }).on('error', err => {
        if(responseSent)  return;
        responseSent = true;
        reject(err);
    });
  });
}

//example
pDownload(url, fileLocation)
  .then( ()=> console.log('downloaded file no issues...'))
  .catch( e => console.error('error while downloading', e));

回答by Feel Physics

Vince Yuan's code is great but it seems to be something wrong.

Vince Yuan 的代码很棒,但似乎有些错误。

function download(url, dest, callback) {
    var file = fs.createWriteStream(dest);
    var request = http.get(url, function (response) {
        response.pipe(file);
        file.on('finish', function () {
            file.close(callback); // close() is async, call callback after close completes.
        });
        file.on('error', function (err) {
            fs.unlink(dest); // Delete the file async. (But we don't check the result)
            if (callback)
                callback(err.message);
        });
    });
}

回答by mixdev

I prefer request() because you can use both http and https with it.

我更喜欢 request() 因为它可以同时使用 http 和 https。

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))