将文件写入文件系统

时间:2019-10-21 17:36:40

标签: javascript node.js promise async-await

我有兴趣了解如何Promisfy此代码块:

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 个答案:

答案 0 :(得分:3)

这是一种手动将管道操作包装在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.promises版本并等待它们,就可以轻松地添加它。


一些注意事项。这主要是错误处理,因为可能会在三个位置出现错误,并且需要为某些错误进行一些清理工作。

  1. 添加了必需的错误处理。

  2. 在OP的原始代码中,它们称为file.close(),但是file是流,并且writeStream上没有.close()方法。您调用.end()以关闭写入​​流。

  3. 您可能还需要检查适当的response.status,因为http.get()仍会返回响应对象并流式传输,即使状态为4xx或5xx之类。

答案 1 :(得分:3)

这是我将您的节点样式回调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.promises命名空间中的FileHandle类,以及Symbol.asyncIterator流类上定义的Readable接口,这使您可以使用data的{​​{1}}事件和一个for await...of循环,并将错误处理从response的{​​{1}}事件传播到error块隐式拒绝底层异步迭代器返回的promise。