Как выполнить рефакторинг следующего фрагмента с помощью async / await - PullRequest
0 голосов
/ 21 июня 2020
import { get } from "request";
import { writeFile } from "fs";

get(
  "https://en.wikipedia.org/wiki/Async/await",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      writeFile("async.js", body, writeErr => {
        if (writeErr) {
          console.error(writeErr);
        } else {
          console.log("File written");
        }
      });
    }
  }
);

Насколько я понимаю, функцию writeFile необходимо запускать асинхронно, потому что ей нужно дождаться завершения функции get (получение URL-адреса). Однако я действительно не уверен, как его реорганизовать как таковой.

Ответы [ 2 ]

1 голос
/ 21 июня 2020
import { get } from "request";
import { writeFile } from "fs/promises";
import { promisify } from "util";

const getAsync = promisify(get);

async function main() {
  let body;
  try {
    ({ body } = await getAsync("https://en.wikipedia.org/wiki/Async/await"));
  } catch(requestErr) {
    console.error(requestErr);
    return;
  }
  
  try {
    await writeFile("async.js", body);

    console.log("File written");
  } catch (writeErr) {
    console.error(writeErr);
  }
}

main();

Generi c руководство по преобразованию любого потока обратного вызова в обещания и async / await

Проверить, есть ли в библиотеке API обещаний

Если да, используйте его. Многие библиотеки имеют двойной интерфейс, где вы можете вызывать их с помощью обратного вызова или вы можете вызывать их без, и они возвращают вам обещание. Или у них может быть альтернативный импорт для получения версии обещания.

Если нет, вам нужно преобразовать обратные вызовы в обещания . Имейте в виду, что это можно сделать с помощью существующей библиотеки.

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

Преобразуйте обратные вызовы в async / await

В общем преобразование выглядит так:

From

callbackAPI(data, function(error, result) {
    if (error) {
        //handle error
    } else {
        //process result
    }
});

To

//assume the promisifiedAPI is the promise returning alternative of callbackAPI

try {
    const result = await promisifiedAPI(data);
    //process result
} catch (error) {
    //handle error
}

Это может потребовать некоторых изменений в зависимости от точного использования, но обычно обратный вызов имеет error и result как два параметра. Это преобразуется в конструкцию try..catch, где теперь обещанный вызов - await ed и возвращает, а затем обрабатывает result, при отсутствии проблемы обрабатывается в try. Обработка ошибок происходит в блоке catch.

Также, если еще нет верхнего уровня await, вам нужно обернуть весь асинхронный c код в async function() {} или async () => {} в противном случае вы не можете использовать await.

Шаги, предпринятые здесь

Проверить, есть ли в библиотеке API обещаний

request

Библиотека не имеет встроенного API обещаний. В документации предлагается использовать библиотеки, конвертирующие вызовы в обещания . Легкий способ - использовать utils.promisify() для достижения этой цели, поскольку он уже находится в Node.JS.

Проверяя это, я также наткнулся на How do you right обещать запрос? (ответ Little Roys) именно об этой библиотеке. Код есть (изменен, чтобы сосредоточиться только на get):

import { get, post } from "request";
import { promisify } from "util";

const [getAsync, postAsync] = [get, post].map(promisify);

или, если мы просто хотим сосредоточиться на get()

import { get } from "request";
import { promisify } from "util";

const getAsync = promisify(get);

Также стоит отметить, что Пакет request устарел. В этом случае это жизнеспособная альтернатива переходу на другую библиотеку. Не нужно, но есть возможность. Я буду использовать его на протяжении всего примера, чтобы продемонстрировать, как происходит нормальное преобразование.

fs

Это действительно имеет API-интерфейс обещаний , и вы можете просто импортировать вещи из "fs/promises". Вот обещанная версия writeFile.

В этом случае достаточно заменить

import { writeFile } from "fs";

на

import { writeFile } from "fs/promises";

Преобразовать обратные вызовы на async / await

require

From
get(
  "https://en.wikipedia.org/wiki/Async/await",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      //omitted for brevitiy
    }
  }
To
let body;
try {
  ({ body } = await getAsync("https://en.wikipedia.org/wiki/Async/await"));
} catch(requestErr) {
  console.error(requestErr);
  return;
}

Это вызовет теперь обещанный getAsync и извлечет body из ответа .

Если есть ошибка, мы обрабатываем ее в блоке catch. return; предназначен для немедленного завершения функции в случае ошибки. Я вернусь к этому позже.

Теперь, когда у нас есть body ответа, мы можем продолжить

fs

From
writeFile("async.js", body, writeErr => {
  if (writeErr) {
    console.error(writeErr);
  } else {
    console.log("File written");
  }
});
To
try {
  await writeFile("async.js", body);

  console.log("File written");
} catch (writeErr) {
  console.error(writeErr);
}

Мы вызываем writeFile, используя await, что гарантирует возобновление выполнения только после завершения операции. Мы не получаем от него ответа, мы просто отслеживаем ошибки с помощью try..catch и обрабатываем их, если они возникают. Если writeFile успешно, мы просто go передаем console.log("File written");

async function main()

async function main() {
  //execute promisified operations
}

main();

Весь блок превращается в функцию asyn c, поэтому await может быть использован. Затем мы вызываем эту функцию. Так это немного читабельнее. Как уже упоминалось, может иметь поддержку верхнего уровня await. Это может быть либо в будущем, либо через транспиляцию. Или, может быть, ваш код уже находится в асинхронной функции. В таких случаях вам не понадобится другая асинхронная функция, но я хотел бы продемонстрировать это на всякий случай.

try {} catch(error) { return; }

Наконец, я хочу обратиться к этой конструкции. Не люблю вложение блоков. Избежать этого можно, если переписать код таким образом (фрагмент кода свернут для краткости):

import { get } from "request";
import { writeFile } from "fs/promises";
import { promisify } from "util";

const getAsync = promisify(get);

async function main() {
  
  try {
    const { body } = await getAsync("https://en.wikipedia.org/wiki/Async/await");
    
    try {
      await writeFile("async.js", body);

      console.log("File written");
    } catch (writeErr) {
      console.error(writeErr);
    }
  } catch(requestErr) {
    console.error(requestErr);
  }
}

main();

Однако это начинает становиться проблемой, если вам нужно несколько вызовов asyn c, которые будут в дальнейшем вкладывать блоки. Я предпочитаю ранний выход через return, который удерживает блоки на одном уровне. Альтернативой является использование оператора throw, если вы предпочитаете (и можете) обрабатывать сбой дальше по цепочке вызовов. Оба достигают одного и того же с точки зрения вложенности.

0 голосов
/ 21 июня 2020

Ищете что-то подобное?

var axios = require("axios");
var fs = require("fs");

const fetchAndWrite = async () => {
  try {
    const { data } = await axios.get(
      "https://en.wikipedia.org/wiki/Async/await"
    );
    await fs.promises.writeFile("/some_directory/async-await.html", data);
  } catch (error) {
    // something bad happened...
  }
};

fetchAndWrite();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...