Правильный синтаксис TryCatch с использованием Async / Await

Мне нравится плоскостность новой функции Async/Await, доступной в Typescript и т. Д. Однако я не уверен, что мне нравится тот факт, что я должен объявить переменную, которую я await на внешней стороне блока try...catch, чтобы использовать ее позже. . Вот так:

let createdUser
try {
    createdUser = await this.User.create(userInfo)
} catch (error) {
    console.error(error)
}

console.log(createdUser)
// business
// logic
// goes
// here

Пожалуйста, поправьте меня, если я ошибаюсь, но, по-видимому, лучше всего не размещать несколько строк бизнес-логики в теле try, поэтому я остаюсь только с альтернативой объявления createdUser вне block, назначив его в блоке, а затем используя его после.

Какая лучшая практика в этом случае?


person freedomflyer    schedule 20.06.2017    source источник
comment
Лучше всего использовать то, что работает, понятно, обслуживается и т. Д. Как мы можем правильно ответить на этот вопрос? Я бы просто использовал var, зная, что переменная будет поднята. Это не так?   -  person Heretic Monkey    schedule 21.06.2017
comment
Команда try / catch должна содержать именно то, для чего вы хотите зафиксировать исключение. Если вы явно ищете ошибки, исходящие от this.User.create(), вы бы не добавляли ничего другого в try / catch. Но также вполне разумно поместить в блок попытки целую кучу логики. Все зависит от того, как / где вы хотите обрабатывать ошибку, и как вы хотите разработать свой код обработки исключений и что имеет смысл для данной операции. Общей передовой практики не существует. ЕДИНАЯ общая передовая практика - убедиться, что вы ловите и обрабатываете все ошибки соответствующим образом.   -  person jfriend00    schedule 21.06.2017
comment
async/await является частью ES2017 (выпуск этого года), а не ES6 (который был выпущен два года назад).   -  person Felix Kling    schedule 21.06.2017


Ответы (4)


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

На самом деле я бы сказал, что это так. Обычно вы хотите, чтобы catch все исключения работали со значением:

try {
    const createdUser = await this.User.create(userInfo);

    console.log(createdUser)
    // business logic goes here
} catch (error) {
    console.error(error) // from creation or business logic
}

Если вы хотите перехватывать и обрабатывать ошибки только из обещания, у вас есть три варианта:

  • Объявите переменную снаружи и перейдите в зависимости от того, было исключение или нет. Это может принимать различные формы, например

    • assign a default value to the variable in the catch block
    • return раньше или повторно throw исключение из блока catch
    • установить флаг, поймал ли блок catch исключение, и проверить его в условии if
    • тест на присвоение значения переменной
      let createdUser; // or use `var` inside the block
      try {
          createdUser = await this.User.create(userInfo);
      } catch (error) {
          console.error(error) // from creation
      }
      if (createdUser) { // user was successfully created
          console.log(createdUser)
          // business logic goes here
      }
    
  • Протестируйте перехваченное исключение на предмет его типа и обработайте или повторно вызовите его в зависимости от этого.

      try {
          const createdUser = await this.User.create(userInfo);
          // user was successfully created
          console.log(createdUser)
          // business logic goes here
      } catch (error) {
          if (error instanceof CreationError) {
              console.error(error) // from creation
          } else {
              throw error;
          }
      }
    

    К сожалению, стандартный JavaScript (все еще) не поддерживает синтаксис для условные исключения.

    Если ваш метод не возвращает обещания, которые отклоняются с достаточно конкретными ошибками, вы можете сделать это самостоятельно, повторно добавив что-то более подходящее в обработчике .catch():

      try {
          const createdUser = await this.User.create(userInfo).catch(err => {
              throw new CreationError(err.message, {code: "USER_CREATE"});
          });
          …
      } …
    

    См. Также Обработку нескольких захватов в цепочке обещаний для версии этого до _13 _ / _ 14_.

  • Используйте then с двумя обратными вызовами вместо _16 _ / _ 17_. Это действительно наименее уродливый способ, и моя личная рекомендация также из-за его простоты и правильности, не полагаясь на помеченные ошибки или взгляды на значение результата, чтобы различать выполнение и отклонение обещания:

      await this.User.create(userInfo).then(createdUser => {
          // user was successfully created
          console.log(createdUser)
          // business logic goes here
      }, error => {
          console.error(error) // from creation
      });
    

    Конечно, у этого есть недостаток, заключающийся в введении функций обратного вызова, то есть вы не можете так легко _19 _ / _ 20_ выполнять циклы или выполнять ранние return из внешней функции.

person Bergi    schedule 20.06.2017
comment
В вашем последнем примере .then() используется для разрешения обещания и предоставления обратного вызова, поэтому, возможно, await там не действует. - person dcorking; 24.01.2018
comment
@dcorking Это await выполнение обещания, возвращенного вызовом .then(…). - person Bergi; 24.01.2018
comment
нужно ли лямбда-выражениям, возвращаемым вызовом .then(), ключевое слово async? - person dcorking; 25.01.2018
comment
@dcorking Только если внутри используется await. - person Bergi; 25.01.2018
comment
Я видел людей, прикрепляющих обработчик catch непосредственно к await. Это хорошая идея или обернуть это внутри try / catch? - person Saroj; 25.02.2019
comment
@Saroj const result = await something().catch(err => fallback); проще, чем let result; try { result = await something(); } catch(err) { result = fallback; }, так что да, в таком случае я считаю это хорошей идеей. - person Bergi; 25.02.2019
comment
@Bergi есть ли недостатки у этого синтаксиса? Например, как вырваться из окружающего пространства? Присоединение обработчика кажется наиболее лаконичным, но гибким подходом. - person Slaiyer; 09.12.2019
comment
@Slaiyer Что вы имеете в виду под вырваться из окружающего пространства? Конечно, вы не можете поместить ключевое слово break в onreject обратный вызов _3 _ / _ 4_, если вам нужно выйти из цикла, используйте традиционный блок try. Если вы говорите о возврате значения в окружающую область видимости, просто return его из обратного вызова и await цепочка обещаний. - person Bergi; 10.12.2019

Другой более простой подход - добавить .catch к функции обещания. бывший:

const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})
person nevf    schedule 16.01.2019
comment
Я никогда об этом не думал, но попробовал, и у него есть интересный побочный эффект: вы можете return получить результат в обратном вызове .catch(), чтобы установить значение. В противном случае возвращается undefined. - person Joe Sadoski; 11.05.2021

Обычно я использую функцию catch() Promise для возврата объекта со свойством error в случае ошибки.

Например, в вашем случае я бы сделал:

const createdUser = await this.User.create(userInfo)
          .catch(error => { error }); // <--- the added catch

if (Object(createdUser).error) {
    console.error(error)
}

Если вам не нравится добавлять вызовы catch(), вы можете добавить вспомогательную функцию к прототипу функции:

Function.prototype.withCatcher = function withCatcher() {
    const result = this.apply(this, arguments);
    if (!Object(result).catch) {
        throw `${this.name}() must return a Promise when using withCatcher()`;
    }
    return result.catch(error => ({ error }));
};

И теперь вы сможете:

const createdUser = await this.User.create.withCatcher(userInfo);
if (Object(createdUser).error) {
    console.error(createdUser.error);
}


РЕДАКТИРОВАТЬ 03/2020

Вы также можете добавить к объекту Promise функцию по умолчанию «перехватить объект ошибки» следующим образом:

Promise.prototype.catchToObj = function catchToObj() {
    return this.catch(error => ({ error }));
};

А затем используйте его следующим образом:

const createdUser = await this.User.create(userInfo).catchToObj();
if (createdUser && createdUser.error) {
    console.error(createdUser.error);
}
person Arik    schedule 12.11.2019
comment
Я использую последний подход, и он дает мне 'catchToObj' is not a function ошибку. - person newguy; 17.05.2020
comment
@newguy catchToObj будет существовать на каждом Promise объекте после того, как вы вызовете первый сегмент кода в моем ответе. Если ваша функция не возвращает Promise, она не сработает. - person Arik; 17.05.2020
comment
Я использую метод create Sequelize, который возвращает Promise<Model>. определение: public static async create(values: object, options: object): Promise<Model> - person newguy; 17.05.2020

@Bergi Ответ хорош, но я думаю, что это не лучший способ, потому что вам нужно вернуться к старому методу then (), поэтому я думаю, что лучший способ - отловить ошибку в функции async

async function someAsyncFunction(){
    const createdUser = await this.User.create(userInfo);

    console.log(createdUser)
}

someAsyncFunction().catch(console.log);
  • Но что, если у нас много await в одной функции и нам нужно отлавливать каждую ошибку?

Вы можете объявить функцию to()

function to(promise) {
    return promise.then(data => {
        return [null, data];
    })
    .catch(err => [err]);
}

А потом

async function someAsyncFunction(){
    let err, createdUser, anotherUser;

    [err, createdUser] = await to(this.User.create(userInfo));

    if (err) console.log(`Error is ${err}`);
    else console.log(`createdUser is ${createdUser}`);


    [err, anotherUser] = await to(this.User.create(anotherUserInfo));

    if (err) console.log(`Error is ${err}`);
    else console.log(`anotherUser is ${anotherUser}`);
}

someAsyncFunction();

Читая это, он: «Подождите, пока this.User.create».

Наконец, вы можете создать модуль to.js или просто использовать await-to-js модуль.

Дополнительную информацию о функции to можно найти в этот пост

person Ivan    schedule 15.02.2019
comment
then не хуже, чем await, потому что старше. Он просто другой и подходит для других целей. С другой стороны, этот await to(…) стиль напоминает стиль nodeback со всеми его недостатками. - person Bergi; 25.02.2019
comment
Кстати, для лучшей производительности и простоты вы должны использовать promise.then(data => [null, data], err => [err, null]); - person Bergi; 25.02.2019
comment
Точно. Он просто другой и подходит для других вещей. await используется для создания кода с синхронным синтаксисом, использование then и его обратный вызов являются более асинхронным синтаксисом. Кстати, спасибо за рекомендацию по простоте кода :) - person Ivan; 27.02.2019