Как отказаться от асинхронного / ожидающего синтаксиса?


284

Как я могу отклонить обещание, возвращаемое функцией async / await?

например, первоначально

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

Перевести на async / await

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

Итак, как я мог должным образом отклонить это обещание в этом случае?


20
Избегайте Promiseконструктора антипаттерна ! Даже первый фрагмент должен был быть написанfoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
— Берги

10
Я думаю, что было бы полезно перевести код этого вопроса в ванильный JS, поскольку этот вопрос не имеет ничего общего с TypeScript. Если я так и сделаю, будет ли это редактирование принято?
— Джейкоб Форд

Ответы:


329

Лучше всего , чтобы throwна Errorупаковку значения, что приводит к отклоненному обещанию с Errorобертыванием значения:

} catch (error) {
    throw new Error(400);
}

Вы также можете просто throwуказать значение, но тогда нет информации о трассировке стека:

} catch (error) {
    throw 400;
}

Либо верните отклоненное обещание с Errorпереносом значения, но это не идиоматично:

} catch (error) {
    return Promise.reject(new Error(400));
}

(Или просто return Promise.reject(400);, но опять же, тогда нет никакой контекстной информации.)

(В вашем случае, поскольку вы используете, TypeScriptи fooзначение retrn равно Promise<A>, вы бы использовали return Promise.reject<A>(400 /*or error*/);)

В async/ awaitситуации это последнее, вероятно, немного семантическое несоответствие, но оно работает.

Если вы бросаться Error, что играет хорошо с чем - нибудь , потребляющим своим fooрезультатом «с с awaitсинтаксисом:

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}

12
А поскольку async / await - это возврат асинхронного потока к синтаксису синхронизации, throwэто лучше, чем Promise.reject()IMO. Ли throw 400это другой вопрос. В OP это отклоняет 400, и мы можем утверждать, что это должно отклонить Errorвместо этого.
— unional

2
Да, однако, если ваша цепочка кода действительно использует async / await, тогда вам будет сложно набрать здесь, позвольте мне продемонстрировать в качестве демонстрации
— union

1
есть ли причина, по которой вы хотели бы выдать новую ошибку, а не ту, которая была выдана вам в блоке catch?
— Адриан М

1
@ Себастьян - Я не знаю, что вы там имеете в виду. В asyncфункциях нет resolveили rejectфункции. Есть returnи throw, которые являются идиоматическими способами разрешить и отклонить asyncобещание функции.
— TJ Crowder

1
@ Jan-PhilipGehrcke - Ты можешь , но я никогда не делаю. Это создает экземпляр, newделает это явным. Также обратите внимание, что вы не можете оставить это, если у вас есть Errorподкласс ( class MyError extends Error), так что ...
— TJ Crowder

146

Вероятно, следует также упомянуть, что вы можете просто связать catch()функцию после вызова асинхронной операции, потому что внутри все еще возвращается обещание.

await foo().catch(error => console.log(error));

Таким образом, вы можете избежать try/catchсинтаксиса, если он вам не нравится.


1
Так что, если я хочу отклонить свою asyncфункцию, я выбрасываю исключение, а затем ловко ловлю его, .catch()как если бы я вернулся Promise.rejectили вызвал reject. Мне это нравится!
— icl7126

7
Я не понимаю, почему это должен быть принятый ответ. Мало того, что принятый очиститель ответа, но он также обрабатывает все возможные awaitсбои в одной программе. Если для каждого awaitне нужны особые случаи, я не понимаю, почему вы хотели бы поймать их таким образом. Просто мне скромное мнение.
— edgaralienfoe

1
@jablesauce для моего варианта использования, мне не только нужно было отлавливать каждый awaitсбой отдельно, но мне также нужно было работать с платформой на основе Promise, которая отклоняла обещания при ошибке.
— Реувен Карасик

Это не сработало для меня. Похоже, не собирается в блоке catch, если URL-адрес не удается. [response] = await oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken , accessTokenSecret) .catch (err => {logger.error ('Невозможно получить разрешения для репозитория', err); обратный вызов (err);})
— sn.anurag

1
здесь не нужно awaitключевое слово.
— Ашиш Рават

12

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

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

Используйте это так в ES7 и в асинхронной функции:

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}

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

8

Лучшим способом написания асинхронной функции было бы возвращение ожидающего обещания с самого начала и последующая обработка как отклонений, так и разрешений в обратном вызове обещания, а не просто выдача отклоненного обещания по ошибке. Пример:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

Затем вы просто цепляете методы на возвращенном обещании:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

Источник - этот урок:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise


5
Вопрос, специально заданный об использовании async / await. Не используя обещания
— Мак

Этот ответ не должен был быть окончательно правильным ответом. Это был ответ поддержки на другие ответы, приведенные выше. Я бы записал это как комментарий, но, учитывая, что у меня есть код, поле ответа - лучшее место.
— OzzyTheGiant

Спасибо за разъяснение. Показывать, как сделать асинхронную функцию, безусловно, полезно. Обновление второго блока кода для использования await будет намного более актуальным и полезным. Приветствия
— Мак

Я отредактировал ваш ответ, чтобы обновить его. Дайте мне знать, если я что-то пропустил
— Мак

4

У меня есть предложение правильно обрабатывать отклонения в новом подходе, не имея нескольких блоков try-catch.

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

Откуда должна быть импортирована функция to.ts :

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

Кредиты отправляются Диме Гроссману по следующей ссылке .


1
Я использую эту конструкцию почти исключительно (намного чище), и есть модуль 'to', который существует уже некоторое время npmjs.com/package/await-to-js . Не нужно отдельное объявление, просто поместите let перед деконструированным назначением. Также можно сделать только let [err]=если только проверка на ошибки.
— ДКеблер

3

Это не ответ на вопрос @TJ Краудера. Просто комментарий, отвечающий на комментарий «И на самом деле, если исключение будет преобразовано в отклонение, я не уверен, действительно ли я обеспокоен, если это ошибка. Мои причины для выдачи только ошибки, вероятно, не применимы. "

если ваш код использует async/ await, то все равно рекомендуется отклонять Errorвместо 400:

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}

3

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

Чтобы убедиться, что я не просто раскалывал волосы, я провел тест производительности трех разных подходов к этому, используя этот код:

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

Некоторые вещи, которые там есть, включены из-за моей неуверенности в отношении интерпретатора Javascript (мне нравится проходить только одну кроличью нору за раз); например, я включил doSomethingфункцию и назначил ее возврат, dummyValueчтобы гарантировать, что условные блоки не будут оптимизированы.

Мои результаты были:

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

Я знаю, что есть много случаев, когда не стоит искать небольшие оптимизации, но в более крупных системах эти вещи могут иметь большое совокупное значение, и это довольно резкое сравнение.

ТАК ... хотя я думаю, что подход принятого ответа является обоснованным в тех случаях, когда вы ожидаете обработки непредсказуемых ошибок в рамках асинхронной функции, в тех случаях, когда отклонение просто означает «вам придется пойти с планом B (или C или D…) «Я думаю, что я предпочел бы отказаться от использования пользовательского объекта ответа.


2
Кроме того, помните, что вам не нужно беспокоиться об обработке непредвиденных ошибок в асинхронной функции, если вызов этой функции находится в блоке try / catch в пределах области действия, поскольку - в отличие от Promises - асинхронные функции выдают свои выброшенные ошибки охватывающая область, где они обрабатываются как ошибки, локальные для этой области. Это один из главных преимуществ асинхронного / ожидания!
— RiqueW

Микробенчмарки - это дьявол. Посмотрите ближе на цифры. Вы должны сделать что-то 1000x, чтобы заметить разницу в 1 мс. Да, добавление throw / catch приведет к отключению функции. Но а) если вы ожидаете чего-то асинхронного, то, вероятно, на несколько порядков потребуется более 0,0005 мсек, чтобы произойти в фоновом режиме. б) вам нужно делать это 1000 раз, чтобы получить здесь разницу в 1 мс.
— Джейми Пэйт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.