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


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.