Promisfy запись файла в файловую систему - PullRequest
1 голос
/ 21 октября 2019

Я заинтересован в понимании того, как обещать этот блок кода:

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 ]

3 голосов
/ 22 октября 2019

Вот как я бы переписал ваш 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();
}

Обратите внимание, что в этом подходе используется класс FileHandle в *Пространство имен 1007 *, а также интерфейс Symbol.asyncIterator, определенный в классе потока Readable, который позволяет использовать события data response сfor await...of зацикливает и распространяет обработку ошибок из события error блока response в catch путем неявного отклонения обещания, возвращаемого базовым асинхронным итератором.

3 голосов
/ 22 октября 2019

Вот способ вручную обернуть операцию конвейера в обещание. К сожалению, большая часть этого - просто обработка ошибок, чтобы покрыть все возможные места, где может произойти ошибка:

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.

...