【问题标题】:How to download a file with Node.js (without using third-party libraries)?如何使用 Node.js 下载文件(不使用第三方库)?
【发布时间】:2012-08-10 07:26:01
【问题描述】:

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

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

【问题讨论】:

  • "用 node.js 下载文件" - 你的意思是 upload 到服务器吗?或使用您的服务器从远程服务器检索文件?还是将文件提供给客户端以从您的 node.js 服务器下载?
  • “我只想从给定的 url 下载文件,然后将其保存到给定的目录,”看起来很清楚。 :)
  • Joseph 错误地断言所有节点进程都是服务器进程
  • @lededje 是什么阻止了服务器进程下载文件并将其保存到服务器上的目录中?这是完全可行的。

标签: javascript node.js download fs


【解决方案1】:

说到处理错误,听请求错误也更好。我什至会通过检查响应代码来验证。此处仅对 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, () => cb(err.message)); // delete the (partial) file and then return the error
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest, () => cb(err.message)); // delete the (partial) file and then return the error
    });
};

尽管这段代码相对简单,我还是建议使用request module,因为它可以处理更多http 本身不支持的协议(你好HTTPS!)。

应该是这样的:

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, () => cb(err.message)); // delete the (partial) file and then return the error
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest, () => cb(err.message)); // delete the (partial) file and then return the error
    });
};

【讨论】:

  • 请求模块只适用于 HTTPs。酷!
  • @ventura 是的,顺便说一句,还有原生的 https 模块现在可以处理安全连接。
  • 毫无疑问更容易出错。无论如何,在任何情况下,如果使用请求模块是一种选择,我都会建议它,因为它的级别更高,因此更容易和高效。
  • @Alex,不,这是一条错误消息,并且有返回。因此,如果response.statusCode !== 200 finish 上的 cb 将永远不会被调用。
  • 感谢您展示使用请求模块的示例。
【解决方案2】:

基于上述其他答案和一些微妙问题,这是我的尝试。

  1. 在访问网络之前使用fs.access检查文件不存在。
  2. 只有在获得200 OK 状态码时才创建fs.createWriteStream。这减少了整理临时文件句柄所需的 fs.unlink 命令数量。
  3. 即使在 200 OK 上,由于 EEXIST 文件已经存在,我们仍然可以使用 reject(想象另一个进程在我们进行网络调用时创建了该文件)。
  4. 如果在标头中提供的链接位置后收到301 Moved Permanently302 Found (Moved Temporarily) 重定向,则递归调用download
  5. 其他一些递归调用download 的问题是他们调用resolve(download) 而不是download(...).then(() => resolve()),因此Promise 将在下载实际完成之前返回。这样,嵌套的承诺链就会以正确的顺序解析。
  6. 异步清理临时文件可能看起来很酷,但我也选择了仅在完成后才拒绝,所以我知道当这个承诺解决或拒绝时,一切都开始完成。
const https = require('https');
const fs = require('fs');

/**
 * Download a resource from `url` to `dest`.
 * @param {string} url - Valid URL to attempt download of resource
 * @param {string} dest - Valid path to save the file.
 * @returns {Promise<void>} - Returns asynchronously when successfully completed download
 */
function download(url, dest) {
  return new Promise((resolve, reject) => {
    // Check file does not exist yet before hitting network
    fs.access(dest, fs.constants.F_OK, (err) => {

        if (err === null) reject('File already exists');

        const request = https.get(url, response => {
            if (response.statusCode === 200) {
       
              const file = fs.createWriteStream(dest, { flags: 'wx' });
              file.on('finish', () => resolve());
              file.on('error', err => {
                file.close();
                if (err.code === 'EEXIST') reject('File already exists');
                else fs.unlink(dest, () => reject(err.message)); // Delete temp file
              });
              response.pipe(file);
            } else if (response.statusCode === 302 || response.statusCode === 301) {
              //Recursively follow redirects, only a 200 will resolve.
              download(response.headers.location, dest).then(() => resolve());
            } else {
              reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
            }
          });
      
          request.on('error', err => {
            reject(err.message);
          });
    });
  });
}

【讨论】:

  • 如果目标文件已经存在,您不应该浪费资源进行下载。如果可能,请先检查。
  • @Phil 真的很好。在陷入递归网络调用之前,我已经使用较早的警卫检查更新了该示例,但成功的 200 文件处理代码保持不变。在这种情况下,这个短路保护语句现在应该可以节省一些时间。
  • @JoshPeak 如果文件在服务器上更新了怎么办。
  • @y_159 好问题...这个解决方案只检查资源的名称。如果服务器上的文件不同,则需要比较修改后的时间戳和内容的哈希值,以使本地缓存无效。正确的缓存失效超出了这个问题的范围,需要它自己的问题,并且取决于服务器如何实现 HTTP ETag 协议。
【解决方案3】:

现代版本(ES6、Promise、Node 12.x+)适用于 https/http。它还支持重定向 302 和 301。我决定不使用 3rd 方库,因为它可以使用标准 Node.js 库轻松完成。

// download.js
import fs from 'fs'
import https from 'https'
import http from 'http'
import { basename } from 'path'
import { URL } from 'url'

const TIMEOUT = 10000

function download (url, dest) {
  const uri = new URL(url)
  if (!dest) {
    dest = basename(uri.pathname)
  }
  const pkg = url.toLowerCase().startsWith('https:') ? https : http

  return new Promise((resolve, reject) => {
    const request = pkg.get(uri.href).on('response', (res) => {
      if (res.statusCode === 200) {
        const file = fs.createWriteStream(dest, { flags: 'wx' })
        res
          .on('end', () => {
            file.end()
            // console.log(`${uri.pathname} downloaded to: ${path}`)
            resolve()
          })
          .on('error', (err) => {
            file.destroy()
            fs.unlink(dest, () => reject(err))
          }).pipe(file)
      } else if (res.statusCode === 302 || res.statusCode === 301) {
        // Recursively follow redirects, only a 200 will resolve.
        download(res.headers.location, dest).then(() => resolve())
      } else {
        reject(new Error(`Download request failed, response status: ${res.statusCode} ${res.statusMessage}`))
      }
    })
    request.setTimeout(TIMEOUT, function () {
      request.abort()
      reject(new Error(`Request timeout after ${TIMEOUT / 1000.0}s`))
    })
  })
}

export default download

感谢 Andrey Tkachenko,感谢我修改了他的 gist

将其包含在另一个文件中并使用

const download = require('./download.js')
const url = 'https://raw.githubusercontent.com/replace-this-with-your-remote-file'
console.log('Downloading ' + url)

async function run() {
  console.log('Downloading file')
  try {
    await download(url, 'server')
    console.log('Download done')
  } catch (e) {
    console.log('Download failed')
    console.log(e.message)
  }
}

run()

【讨论】:

  • 太棒了。很干净,谢谢。创建 writeStream 时,标志 'wx' 做了什么?
  • 'wx': 类似于 'w' 但如果路径存在则失败。
【解决方案4】:

我发现这种方法最有用,尤其是在处理 pdf 和其他随机文件时。

import fs from "fs";

  fs.appendFile("output_file_name.ext", fileDataInBytes, (err) => {
    if (err) throw err;
    console.log("File saved!");
  });

【讨论】:

    【解决方案5】:

    使用 http2 模块

    我使用 httphttpsrequest 模块看到了答案。我想使用另一个支持 http 或 https 协议的本机 NodeJS 模块添加一个:

    解决方案

    对于我正在做的事情,我已经参考了官方的 NodeJS API,以及关于这个问题的一些其他答案。以下是我为尝试而编写的测试,它按预期工作:

    import * as fs from 'fs';
    import * as _path from 'path';
    import * as http2 from 'http2';
    
    /* ... */
    
    async function download( host, query, destination )
    {
        return new Promise
        (
            ( resolve, reject ) =>
            {
                // Connect to client:
                const client = http2.connect( host );
                client.on( 'error', error => reject( error ) );
    
                // Prepare a write stream:
                const fullPath = _path.join( fs.realPathSync( '.' ), destination );
                const file = fs.createWriteStream( fullPath, { flags: "wx" } );
                file.on( 'error', error => reject( error ) );
    
                // Create a request:
                const request = client.request( { [':path']: query } );
    
                // On initial response handle non-success (!== 200) status error:
                request.on
                (
                    'response',
                    ( headers/*, flags*/ ) =>
                    {
                        if( headers[':status'] !== 200 )
                        {
                            file.close();
                            fs.unlink( fullPath, () => {} );
                            reject( new Error( `Server responded with ${headers[':status']}` ) );
                        }
                    }
                );
    
                // Set encoding for the payload:
                request.setEncoding( 'utf8' );
    
                // Write the payload to file:
                request.on( 'data', chunk => file.write( chunk ) );
    
                // Handle ending the request
                request.on
                (
                    'end',
                    () =>
                    {
                        file.close();
                        client.close();
                        resolve( { result: true } );
                    }
                );
    
                /* 
                    You can use request.setTimeout( 12000, () => {} ) for aborting
                    after period of inactivity
                */
    
                // Fire off [flush] the request:
                request.end();
            }
        );
    }
    

    那么,例如:

    /* ... */
    
    let downloaded = await download( 'https://gitlab.com', '/api/v4/...', 'tmp/tmpFile' );
    
    if( downloaded.result )
    {
        // Success!
    }
    
    // ...
    

    外部参考

    编辑信息

    • 该解决方案是为 typescript 编写的,该函数是一个 类方法 - 但如果不注意这一点,如果没有正确使用 function 声明,该解决方案将不适用于假定的 javascript 用户,我们的贡献者如此迅速地添加了它。谢谢!

    【讨论】:

    • 这是向后兼容的吗?适用于任何地方或仅适用于 http2?
    • @Neil 我假设您在询问 http2 是否向后兼容 http1.2 - 答案是否定的。因为http2改进了框架;添加二进制压缩、从服务器推送到客户端的能力以及同时连接 - 绝对要求服务器和客户端都知道实现(这也允许从应用程序中抽象实现)。好消息是,自 2015 年左右以来,所有主流浏览器都支持 http2 - 作为客户端的 Node 也支持。 Node、Nginx 和 Apache 提供服务器端 - 因此涵盖了大多数用例。这是一个巨大的进步。
    【解决方案6】:

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

    const http = require('http'); // or 'https' for https:// URLs
    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);
    });
    

    如果您想支持在命令行上收集信息(例如指定目标文件或目录或 URL),请查看 Commander 之类的内容。

    【讨论】:

    • 我在运行此脚本时得到以下控制台输出:node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: connect ECONNREFUSED at errnoException (net.js:646:11) at Object.afterConnect [as oncomplete] (net.js:637:18)
    • 此代码在脚本结束时是否正确关闭文件,还是会丢失数据?
    • @quantumpotato 看看您从请求中得到的响应
    • 这取决于req url类型如果你请求https你必须使用https否则会抛出错误。
    • @EthanKeiley 为什么说它没有正确关闭?默认情况下,createWriteStream 会将autoClose 设置为truereadable.pipe 将在可写对象上调用end(),当可读对象结束时。
    【解决方案7】:

    我建议你使用res.download,如下:

    app.get('/download', function(req, res){
      const file = `${__dirname}/folder/abc.csv`;
      res.download(file); // Set disposition and send it.
    });
    

    【讨论】:

      【解决方案8】:

      编写我自己的解决方案,因为现有的不符合我的要求。

      涵盖的内容:

      • HTTPS 下载(将包切换到 http 以进行 HTTP 下载)
      • 基于承诺的函数
      • 处理转发路径(状态 302)
      • 浏览器标头 - 一些 CDN 需要
      • 来自 URL 的文件名(以及硬编码)
      • 错误处理

      它是打字的,它更安全。如果您使用纯 JS(无 Flow,无 TS)或转换为 .d.ts 文件,请随意删除类型

      index.js

      import httpsDownload from httpsDownload;
      httpsDownload('https://example.com/file.zip', './');
      

      httpsDownload.[js|ts]

      import https from "https";
      import fs from "fs";
      import path from "path";
      
      function download(
        url: string,
        folder?: string,
        filename?: string
      ): Promise<void> {
        return new Promise((resolve, reject) => {
          const req = https
            .request(url, { headers: { "User-Agent": "javascript" } }, (response) => {
              if (response.statusCode === 302 && response.headers.location != null) {
                download(
                  buildNextUrl(url, response.headers.location),
                  folder,
                  filename
                )
                  .then(resolve)
                  .catch(reject);
                return;
              }
      
              const file = fs.createWriteStream(
                buildDestinationPath(url, folder, filename)
              );
              response.pipe(file);
              file.on("finish", () => {
                file.close();
                resolve();
              });
            })
            .on("error", reject);
          req.end();
        });
      }
      
      function buildNextUrl(current: string, next: string) {
        const isNextUrlAbsolute = RegExp("^(?:[a-z]+:)?//").test(next);
        if (isNextUrlAbsolute) {
          return next;
        } else {
          const currentURL = new URL(current);
          const fullHost = `${currentURL.protocol}//${currentURL.hostname}${
            currentURL.port ? ":" + currentURL.port : ""
          }`;
          return `${fullHost}${next}`;
        }
      }
      
      function buildDestinationPath(url: string, folder?: string, filename?: string) {
        return path.join(folder ?? "./", filename ?? generateFilenameFromPath(url));
      }
      
      function generateFilenameFromPath(url: string): string {
        const urlParts = url.split("/");
        return urlParts[urlParts.length - 1] ?? "";
      }
      
      export default download;
      

      【讨论】:

        【解决方案9】:

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

        如果不等待finish 事件,天真的脚本可能会以不完整的文件告终。

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

        【讨论】:

        • 回调让我很困惑。如果我现在调用download(),我会怎么做?我会将什么作为 cb 参数?我有download('someURI', '/some/destination', cb),但不明白在 cb 中放什么
        • @Abdul 仅当成功获取文件后需要执行某些操作时才使用函数指定回调。
        • 保存前最好检查状态码:response.statusCode == 200
        【解决方案10】:

        download.js(即 /project/utils/download.js)

        const fs = require('fs');
        const request = require('request');
        
        const download = (uri, filename, callback) => {
            request.head(uri, (err, res, body) => {
                console.log('content-type:', res.headers['content-type']);
                console.log('content-length:', res.headers['content-length']);
        
                request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
            });
        };
        
        module.exports = { download };
        


        app.js

        ... 
        // part of imports
        const { download } = require('./utils/download');
        
        ...
        // add this function wherever
        download('https://imageurl.com', 'imagename.jpg', () => {
          console.log('done')
        });
        

        【讨论】:

          【解决方案11】:

          ✅因此,如果您使用pipeline,它将关闭所有其他流并确保没有内存泄漏。

          工作示例:

          const http = require('http');
          const { pipeline } = require('stream');
          const fs = require('fs');
          
          const file = fs.createWriteStream('./file.jpg');
          
          http.get('http://via.placeholder.com/150/92c952', response => {
            pipeline(
              response,
              file,
              err => {
                if (err)
                  console.error('Pipeline failed.', err);
                else
                  console.log('Pipeline succeeded.');
              }
            );
          });
          

          my answer"What's the difference between .pipe and .pipeline on streams"

          【讨论】:

            【解决方案12】:

            这是另一种处理它的方法,无需 3rd 方依赖并搜索重定向:

                    var download = function(url, dest, cb) {
                        var file = fs.createWriteStream(dest);
                        https.get(url, function(response) {
                            if ([301,302].indexOf(response.statusCode) !== -1) {
                                body = [];
                                download(response.headers.location, dest, cb);
                              }
                          response.pipe(file);
                          file.on('finish', function() {
                            file.close(cb);  // close() is async, call cb after close completes.
                          });
                        });
                      }
            
            

            【讨论】:

              【解决方案13】:

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

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

              【讨论】:

              • 看起来 Request 已被弃用 github.com/request/request/issues/3142 "As of Feb 11th 2020, request is fully deprecated. No new changes are expected to land. In fact, none have landed for some time."
              【解决方案14】:

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

              一个完整的解决方案是:

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

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

              【讨论】:

              • 你将请求存储到变量中是为了什么?
              • 他将其“存储”到一个变量中,因此默认情况下它不会成为全局变量。
              • @philk 如果删除了var request =,你怎么知道创建了一个全局变量?
              • 你是对的,不需要保存请求,反正它没有被使用。这就是你的意思?
              • @philk 谢谢。是的,我认为这就是polkovnikov.ph 的意思。
              【解决方案15】:

              你好,我想你可以使用 child_process 模块和 curl 命令。

              const cp = require('child_process');
              
              let download = async function(uri, filename){
                  let command = `curl -o ${filename}  '${uri}'`;
                  let result = cp.execSync(command);
              };
              
              
              async function test() {
                  await download('http://zhangwenning.top/20181221001417.png', './20181221001417.png')
              }
              
              test()
              

              另外,当你想下载大、多个文件时,可以使用cluster模块来使用更多的cpu核心。

              【讨论】:

                【解决方案16】:
                var fs = require('fs'),
                    request = require('request');
                
                var download = function(uri, filename, callback){
                    request.head(uri, function(err, res, body){
                    console.log('content-type:', res.headers['content-type']);
                    console.log('content-length:', res.headers['content-length']);
                    request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
                
                    }); 
                };   
                
                download('https://www.cryptocompare.com/media/19684/doge.png', 'icons/taskks12.png', function(){
                    console.log('done');
                });
                

                【讨论】:

                  【解决方案17】:

                  如果您使用的是 express 使用 res.download() 方法。否则使用 fs 模块。

                  app.get('/read-android', function(req, res) {
                     var file = "/home/sony/Documents/docs/Android.apk";
                      res.download(file) 
                  }); 
                  

                  (或)

                     function readApp(req,res) {
                        var file = req.fileName,
                            filePath = "/home/sony/Documents/docs/";
                        fs.exists(filePath, function(exists){
                            if (exists) {     
                              res.writeHead(200, {
                                "Content-Type": "application/octet-stream",
                                "Content-Disposition" : "attachment; filename=" + file});
                              fs.createReadStream(filePath + file).pipe(res);
                            } else {
                              res.writeHead(400, {"Content-Type": "text/plain"});
                              res.end("ERROR File does NOT Exists.ipa");
                            }
                          });  
                      }
                  

                  【讨论】:

                    【解决方案18】:

                    您可以尝试使用res.redirect 到 https 文件下载 url,然后它将下载文件。

                    点赞:res.redirect('https//static.file.com/file.txt');

                    【讨论】:

                      【解决方案19】:

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

                      以下代码基于 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();
                          });
                      });
                      

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

                      【讨论】:

                      • 这只是一个文件,没有可供下载的协议或服务器...http.get("http://example.com/yourfile.html",function(){})
                      • 这个答案是否存在内存泄漏:stackoverflow.com/a/22793628/242933?
                      • 您可以像我在http.get 中那样添加超时。仅当文件下载时间过长时才会发生内存泄漏。
                      【解决方案20】:
                      function download(url, dest, cb) {
                      
                        var request = http.get(url, function (response) {
                      
                          const settings = {
                            flags: 'w',
                            encoding: 'utf8',
                            fd: null,
                            mode: 0o666,
                            autoClose: true
                          };
                      
                          // response.pipe(fs.createWriteStream(dest, settings));
                          var file = fs.createWriteStream(dest, settings);
                          response.pipe(file);
                      
                          file.on('finish', function () {
                            let okMsg = {
                              text: `File downloaded successfully`
                            }
                            cb(okMsg);
                            file.end(); 
                          });
                        }).on('error', function (err) { // Handle errors
                          fs.unlink(dest); // Delete the file async. (But we don't check the result)
                          let errorMsg = {
                            text: `Error in file downloadin: ${err.message}`
                          }
                          if (cb) cb(errorMsg);
                        });
                      };
                      

                      【讨论】:

                        【解决方案21】:

                        如果没有库,仅指出可能是错误的。以下是一些:

                        这是我的建议:

                        • 调用wgetcurl等系统工具
                        • 使用一些工具,如node-wget-promise,使用起来也很简单。 var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');

                        【讨论】:

                          【解决方案22】:

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

                          1. 您无需在finish 事件中调用file.close()。默认情况下,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文件存在;它被覆盖

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

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

                          【讨论】:

                          • 对此有两个 cmets:1) 它可能应该拒​​绝 Error 对象,而不是字符串,2) fs.unlink 会悄悄地吞下可能不一定是您想要做的错误
                          • 这很好用!如果您的 URL 使用 HTTPS,只需将 const https = require("https"); 替换为 const http = require("http");
                          • 根据fs.createWriteStream()documentation:“如果在'error' 或'finish' 时将autoClose 设置为true(默认行为),文件描述符将自动关闭。”所以不需要出错时手动关闭文件。
                          【解决方案23】:
                          var requestModule=require("request");
                          
                          requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
                          

                          【讨论】:

                          • 代码转储通常没有用,可能会被否决或删除。值得编辑至少解释一下代码为未来的访问者做了什么。
                          【解决方案24】:
                          const download = (url, path) => new Promise((resolve, reject) => {
                          http.get(url, response => {
                              const statusCode = response.statusCode;
                          
                              if (statusCode !== 200) {
                                  return reject('Download error!');
                              }
                          
                              const writeStream = fs.createWriteStream(path);
                              response.pipe(writeStream);
                          
                              writeStream.on('error', () => reject('Error writing to file!'));
                              writeStream.on('finish', () => writeStream.close(resolve));
                          });}).catch(err => console.error(err));
                          

                          【讨论】:

                            【解决方案25】:

                            使用 Promise 下载,它解析一个可读流。添加额外的逻辑来处理重定向。

                            var http = require('http');
                            var promise = require('bluebird');
                            var url = require('url');
                            var fs = require('fs');
                            var assert = require('assert');
                            
                            function download(option) {
                                assert(option);
                                if (typeof option == 'string') {
                                    option = url.parse(option);
                                }
                            
                                return new promise(function(resolve, reject) {
                                    var req = http.request(option, function(res) {
                                        if (res.statusCode == 200) {
                                            resolve(res);
                                        } else {
                                            if (res.statusCode === 301 && res.headers.location) {
                                                resolve(download(res.headers.location));
                                            } else {
                                                reject(res.statusCode);
                                            }
                                        }
                                    })
                                    .on('error', function(e) {
                                        reject(e);
                                    })
                                    .end();
                                });
                            }
                            
                            download('http://localhost:8080/redirect')
                            .then(function(stream) {
                                try {
                            
                                    var writeStream = fs.createWriteStream('holyhigh.jpg');
                                    stream.pipe(writeStream);
                            
                                } catch(e) {
                                    console.error(e);
                                }
                            });
                            

                            【讨论】:

                            • 302 也是 URL 重定向的 HTTP 状态码,所以你应该在 if 语句中使用这个 [301,302].indexOf(res.statusCode) !== -1
                            • 问题是特定于不包括第三方模式:)
                            【解决方案26】:

                            对于那些寻求基于 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));
                            

                            【讨论】:

                            • responseSet 标志由于某种我没有时间调查的原因导致我的文件下载不完整。没有弹出错误,但我填充的 ​​.txt 文件有一半需要存在的行。删除标志的逻辑修复了它。只是想指出是否有人对这种方法有疑问。不过,+1
                            【解决方案27】:

                            路径:img 类型:jpg 随机uniqid

                                function resim(url) {
                            
                                var http = require("http");
                                var fs = require("fs");
                                var sayi = Math.floor(Math.random()*10000000000);
                                var uzanti = ".jpg";
                                var file = fs.createWriteStream("img/"+sayi+uzanti);
                                var request = http.get(url, function(response) {
                              response.pipe(file);
                            });
                            
                                    return sayi+uzanti;
                            }
                            

                            【讨论】:

                              【解决方案28】:

                              您可以使用https://github.com/douzi8/ajax-request#download

                              request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
                                function(err, res, body) {}
                              );
                              

                              【讨论】:

                              • 如果文件名不是 ascii 则返回垃圾字符,就像文件名是日文一样。
                              • 你认为ajax-request不是第三方库吗?
                              【解决方案29】:

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

                              【讨论】:

                              • 我们可以指定目标文件夹吗?
                              【解决方案30】:

                              不要忘记处理错误!以下代码基于 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);
                                });
                              };
                              

                              【讨论】:

                              • @vince-yuan 是download() 本身pipeable?
                              • @VinceYuan 回调让我很困惑。如果我现在调用download(),我会怎么做?我会将什么作为 cb 参数?我有download('someURI', '/some/destination', cb),但不明白在 cb 中放什么
                              • @Abdul 听起来您对 node.js/javascript 很陌生。看看这个教程:tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm 并不复杂。
                              • @Abdul 如果您与全班其他同学分享您的发现也许会更好?
                              • 有没有办法查看下载速度?像可以跟踪多少mb / s?谢谢!
                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 2020-04-29
                              • 1970-01-01
                              • 1970-01-01
                              • 2016-07-18
                              • 2017-11-03
                              • 2016-08-31
                              • 2017-05-18
                              相关资源
                              最近更新 更多