提问者:小点点

允许将文件写入文件系统


我感兴趣的是理解如何允诺这段代码:

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

const download = function(url, dest, cb) {
  let file = fs.createWriteStream(dest);
  const 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);
  });
};

我对此的第一个看法是:

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

const download = async (url, dest, cb) => {
  let file = fs.createWriteStream(dest);
  const request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      const closed = await file.close(cb);  // close() is async, await here?
      if (closed) {
          // handle cleanup and retval
      }
    });
  }).on('error', function(err) { // Handle errors
    const deleted = await fs.unlink(dest); // Delete the file async. 
    if (!deleted) { ... }
  });
};

上面的实施显然是错误的。要移除回调,只使用async/await,正确的方法是什么?


共2个答案

匿名用户

这里有一种方法可以手动将管道操作包装在Promise中。不幸的是,这大部分只是错误处理,以覆盖所有可能发生错误的地方:

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

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

      // centralize error cleanup function
      function cleanup(err) {
          reject(err);
          // cleanup partial results when aborting with an error
          file.on('close', () => {
            fs.unlink(dest);
          });
          file.end();
      }

      file.on('error', cleanup).on('finish', resolve);

      const request = http.get(url, function(response) {
        if (response.status < 200 || response.status >= 300) {
            cleanup(new Error(`Unexpected Request Status Code: ${response.status}`);
            return;
        }
        response.pipe(file);
        response.on('error', cleanup);

      }).on('error', cleanup);
  });
};

download(someURL, someDest).then(() => {
  console.log("operation complete");
}).catch(err => {
  console.log(err);
});

这并不是在拒绝之前等待文件在错误条件下被关闭或删除(如果这些清理操作仍然有错误,通常没有什么建设性的操作)。如果需要,只需从这些清理操作的异步回调中调用reject(err),或者使用这些函数的fs.promisions版本并等待它们,就可以很容易地添加它。

有几件事要注意。这主要是错误处理,因为有三个可能的地方您可以有错误,一些错误需要一些清理工作。

>

  • 添加了所需的错误处理。

    在OP的原始代码中,他们调用了file.clos(),但file是一个流,在WriteStream上没有.clos()方法。调用.end()关闭写入流。

    您可能还需要检查是否有适当的response.status,因为http.get()仍然返回响应对象和流,即使状态类似于4xx或5xx。

  • 匿名用户

    下面是我如何将节点样式的回调API重写为异步函数:

    const http = require('http');
    const fs = require('fs');
    
    async function download (url, dest) {
      const response = await new Promise((resolve, reject) => {
        http.get(url, resolve).once('error', reject);
      });
    
      if (response.status < 200 || response.status >= 300) {
        throw new Error(`${responses.status} ${http.STATUS_CODES[response.status]}`);
      }
    
      const file = await fs.promises.open(dest, 'w');
    
      try {
        for await (const data of response) {
          await file.write(data);
        }
      } catch (error) {
        await file.close();
        await fs.promises.unlink(dest);
        throw error;
      }
    
      await file.close();
    }
    

    请注意,此方法使用fs.promissions命名空间中的filehandle类,以及在readable流类上定义的symbol.AsyncIterator接口,该接口允许您使用具有for await.。。of循环的responsdata事件,并通过隐式拒绝基础异步迭代器返回的promise,将错误处理从responserror