Async/await преимущества, подводные камни и как их использовать

внешний интерфейс Безопасность JavaScript Язык программирования

У меня в последнее время много дел, давно не учился, да и сам влепилмаленький сайт, добро пожаловать звезда.

JavaScript async/await: The Good Part, Pitfalls and How to Use

Представлено ES7async/awaitФункции — это серьезное улучшение асинхронного программирования в JS. Это дает нам возможность получать ресурсы асинхронно, используя стиль синхронного кода, не блокируя основной поток. Конечно, его использование также требует некоторых навыков, и в этой статье мы рассмотрим его с разных сторон.async/await, чтобы показать вам, как использовать их правильно и эффективно.

async/awaitпреимущество

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

// async/await
async getBooksByAuthorWithAwait(authorId) {
  const books = await bookModel.fetchAll();
  return books.filter(b => b.authorId === authorId);
}
// promise
getBooksByAuthorWithPromise(authorId) {
  return bookModel.fetchAll()
    .then(books => books.filter(b => b.authorId === authorId));
}

очевидно,async/awaitсоотношение версийpromiseВерсия проще и понятнее. если вы проигнорируетеawaitключевое слово, то код похож на другие синхронные языки программирования, такие какPython.

Преимущество не только в удобочитаемости,async/awaitУже изначально поддерживается браузерами. Сегодня все основные браузеры имеютполностью поддерживаю.

Встроенная поддержка означает, что вам не нужноконвертироватькода и, что более важно, облегчает отладку. когда вы находитесь в функцииawaitКогда вы нажмете точку останова на строке кода и перейдете к следующей строке, вы обнаружите, что отладчикbookModel.fetchAll()Во время операции он ненадолго остановился, а потом действительно вошел в.filterстрока кода! это лучше, чемpromiseОтладка проще, потому что вам нужно.fliterНажмите еще одну точку останова в строке кода.

Еще одно малозаметное преимущество заключается в том, чтоasyncключевые слова. это показываетgetBooksByAuthorWithAwait()Возвращаемое значение функции должно бытьpromise, поэтому вызывающая сторона может использоватьgetBooksByAuthorWithAwait().then(...)или безопасно использоватьawait getBooksByAuthorWithAwait(). См. код (плохая практика!):

getBooksByAuthorWithPromise(authorId) {
  if (!authorId) {
    return null;
  }
  return bookModel.fetchAll()
    .then(books => books.filter(b => b.authorId === authorId));
  }
}

Во фрагменте кода вышеgetBooksByAuthorWithPromiseможет вернутьpromise(нормальный) илиnullзначение (исключение), и в этом случае вызывающая сторона не может безопасно использовать.then(). и имеютasyncзаявление, чтобы избежать этой неопределенности.

async/awaitиногда вводящий в заблуждение

Некоторые статьи будут сравниватьasync/awaitа такжеpromiseи утверждают, что это следующее поколениеJSАсинхронное программирование, с которым я не согласен.async/await КонечноУлучшение, но это всего лишь синтаксический сахар, и он не изменит полностью наш стиль программирования.

По сути,asyncфункция по-прежнемуpromises. при правильном использованииasyncПрежде чем вам нужно понятьpromise, вероятно, вы используетеasyncтакже нужно использоватьpromise.

Просмотрите код вышеgetBooksByAuthorWithAwait()а такжеgetBooksByAuthorWithPromises()функции, они не только функционируют точно так же, но и имеют такой же интерфейс.

Это означает, что вызов напрямуюgetBooksByAuthorWithAwait()вернетpromise.

Это не обязательно плохо, и большинство людей думают, чтоawaitИдея, что асинхронную функцию можно сделать синхронной, неверна.

async/awaitловушка

где мы используемasync/awaitКакие ошибки будут допущены? Вот некоторые общие моменты.

слишком синхронизировано

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

async getBooksAndAuthor(authorId) {
  const books = await bookModel.fetchAll();
  const author = await authorModel.fetch(authorId);
  return {
    author,
    books: books.filter(book => book.authorId === authorId),
  };
}

Этот код выглядит хорошо, но он неверен.

  1. await bookModel.fetchAll()будет ждатьfetchAll()вернуть
  2. Незамедлительно послеawait authorModel.fetch(authorId)будет называться

уведомлениеauthorModel.fetch(authorId)не зависит отbookModel.fetchAll()результатов, ведь их можно выполнять параллельно!awaitприведет к двум функциям串行执行, а время выполнения будет больше, чем并行执行длинная.

Это правильный способ сделать это:

async getBooksAndAuthor(authorId) {
  const bookPromise = bookModel.fetchAll();
  const authorPromise = authorModel.fetch(authorId);
  const book = await bookPromise;
  const author = await authorPromise;
  return {
    author,
    books: books.filter(book => book.authorId === authorId),
  };
}

И если вы хотите получить все элементы списка по очереди, вам придется полагаться наpromises:

async getAuthors(authorIds) {
  // 错误,这会导致`串行执行`
  // const authors = _.map(
  //   authorIds,
  //   id => await authorModel.fetch(id));

  // 正确
  const promises = _.map(authorIds, id => authorModel.fetch(id));
  const authors = await Promise.all(promises);
}

Короче говоря, вам все равно нужно относиться к рабочему процессу как к асинхронному и пытаться использоватьawaitдля написания синхронного кода. В более сложных рабочих процессах используйте напрямуюpromiseМожет быть удобнее.

обработка ошибок

комбинироватьpromises, асинхронная функция имеет только два возможных возвращаемых значения:resolve值а такжеreject值, то мы можем использовать.then()справляться с обычными ситуациями,.catch()Обрабатывать исключения. ноasync/awaitОбработка ошибок требует некоторого навыка.

try...catch

Самый распространенный (и я рекомендую) способ - использоватьtry..catch. когдаawaitВо время операции любоеreject值будет выброшено как исключение. См. код:

class BookModel {
  fetchAll() {
    return new Promise((resolve, reject) => {
      window.setTimeout(() => { reject({'error': 400}) }, 1000);
    });
  }
}
// async/await
async getBooksByAuthorWithAwait(authorId) {
try {
  const books = await bookModel.fetchAll();
} catch (error) {
  console.log(error);    // { "error": 400 }
}

Объект ошибки вывода точноreject值. После перехвата исключений мы можем обработать их, используя:

  • Обработать исключение и вернуть нормальное значение (вcatchкодовые блоки не используютсяreturnзаявление эквивалентноreturn undefined;, конечно, это нормальное значение).
  • Бросьте, если вы хотите, чтобы вызывающий объект обрабатывал исключение. Вы можете напрямую выбросить объект исключения, напримерthrow error, что позволяет вамasync getBooksByAuthorWithAwait()функция для использованияpromiseцепные операции (т.е.:getBooksByAuthorWithAwait().then(...).catch(error => ...)); или используйтеErrorobject обертывает ваш объект ошибки, напримерthrow new Error(error), поэтому вы можете увидеть полный журнал стека при просмотре ошибки в консоли.
  • rejectобъект ошибки, напримерreturn Promise.reject(error). Это эквивалентно первому подходу, поэтому не рекомендуется.

использоватьtry...catchПреимущества заключаются в следующем:

  • Простой, традиционный, если у вас есть такие вещи, какJavaилиC++Опыт программирования, легко понять.
  • вtry...catchВ блоке кода вы можетеtryБлоки кода переносятся на несколько строкawaitзаявление, и если предварительная обработка ошибок не требуется, вы можете сделать это в одном месте (т.е.catchблок кода) для обработки ошибок.

Эта схема все же имеет свои недостатки,try...catchМожет отловить все ошибки в блоке кода, в том числе те, которые неpromisesОшибки пойманы. См. код:

class BookModel {
  fetchAll() {
    cb();    // `cb` 因为没有被定义所有会导致异常
    return fetch('/books');
  }
}
try {
  bookModel.fetchAll();
} catch(error) {
  console.log(error);  // 这里打印 "cb is not defined"
}

Запустите этот код, и вы получите в консолиReferenceError: cb is not definedВывод информации черным шрифтом. Вы должны знать, что ошибка здесь черезconsole.log()вывод, неJSсама бросает(JSОшибки выделены красным шрифтом). Иногда это может быть фатальным: еслиBookModelБудучи глубоко вложенными и обернутыми вызовами некоторых других функций, один из которых проглатывает исключение, становится крайне сложно найти в примере такую ​​ошибку.

заставить функцию возвращать все значения

кGoЯзыковая эвристика, еще один способ обработки ошибок — разрешитьasyncвозврат функции异常а также结果два значения (см.How to write async await without try-catch blocks in Javascript), то есть можно использоватьasyncфункция:

[err, user] = await to(UserModel.findById(1));

Я лично не рекомендую использовать эту реализацию, поскольку она ставитGoязыковой стиль принесJS, что кажется мне неестественным, но в отдельных случаях крайне уместно использовать.

использовать.catch()

Последний метод заключается в продолжении использования.catch().

передуматьawaitчто он делает: он ждетpromiseвыполнить работу, также помнитеpromise.catch()также вернетpromise! Таким образом, мы можем обрабатывать ошибки с помощью этого:

// 如果发生异常,但是 catch 语句没有显示返回,那么 books === undefined
let books = await bookModel.fetchAll()
  .catch((error) => { console.log(error); });

У этой реализации есть два недостатка:

  • этоpromiseа такжеasyncфункция перемешивания. тебе нужно понятьpromiseпонять это.
  • Обработка ошибок перед возвратом, что не очень интуитивно понятно.

В заключение

ES7изasync/awaitпара функцийJSАсинхронное программирование — это огромное улучшение. Это делает код более читабельным и упрощает отладку. Но чтобы правильно ими пользоваться, нужно досконально пониматьpromise. Поскольку это всего лишь синтаксический сахар, технология, на которую он опирается, по-прежнему актуальна.promise.