Як відхилити синтаксис async / wait?


282

Як я можу відхилити обіцянку, повернуту функцією асинхрон / очікування?

наприклад, спочатку

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

Перевести на асинхронізацію / очікувати

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. Якби я це зробив, чи буде прийнята редакція?
— Джейкоб Форд

Відповіді:


328

Ваша найкраща ставка - це 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 / wait очікує повернення потоку async для синхронізації синтаксису, throwце краще, ніж Promise.reject()IMO. Чи throw 400інше питання. В ОП він відхиляє 400, і ми можемо стверджувати, що він повинен відхилити Errorзамість цього.
— unional

2
Так, якщо ваш кодовий ланцюг дійсно використовує async / wait, тоді вам буде важко набрати тут, дозвольте мені демонструвати як відповідь
— unional

1
Чи є якась причина, що ви хочете викинути нову помилку на відміну від помилки, наданої вам у блоці лову?
— Адріан М

1
@sebastian - я не знаю, що ти там маєш на увазі. У 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невдачу окремо, але мені також потрібно було працювати з основою на Обіцях, яка відкидала обіцянки про помилку.
— Реувен Карасик

Це не працювало для мене. Схоже, це не працює в блоці спіймання, якщо URL не працює. [відповідь] = очікувати oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (err => {logger.error ('Неможливо отримати дозволи на сховище', помилка); зворотний виклик (
— помилка

1
awaitтут не потрібне ключове слово
— Ashish

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 / wait. Не використовуючи обіцянок
— Мак

Ця відповідь не мала бути остаточною правильною відповіддю. Це було підтримкою відповіді на інші відповіді, наведені вище. Я б написав це як коментар, але враховуючи, що у мене є код, поле відповідей - це краще місце.
— OzzyTheGiant

Дякуємо за уточнення. Показати, як зробити функцію асинхронізації, безумовно, корисно. Оновлення другого блоку коду для використання очікування буде набагато релевантнішим та кориснішим. Ура
— Мак

Я відредагував вашу відповідь, щоб оновити її. Повідомте мене, якщо я щось пропустив
— Мак,

4

У мене є пропозиція правильно обробити відхилення в новому підході, не маючи декількох блоків спробу-лову.

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
Я використовую цю конструкцію майже виключно (набагато чистіше), і є модуль "до", який існує протягом деякого часу npmjs.com/package/await-to-js . Окрему декларацію не потрібно просто виставити перед деконструйованим завданням. Також може робити, let [err]=якщо тільки перевірка на помилки.
— DKebler

3

Це не відповідь на відповідь @TJ Crowder. Просто коментар у відповідь на коментар "І насправді, якщо виняток буде перетворено на відмову, я не впевнений, чи насправді турбує, якщо це помилка. Мої причини викидання лише Помилки, ймовірно, не застосовуються. "

якщо ваш код використовує 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
Крім того, пам’ятайте, що вам не потрібно підкреслювати обробку непередбачуваних помилок у функції асинхронізації, якщо виклик цієї функції знаходиться в блоці спробу / лову в області, що додається, оскільки - на відміну від Обіцянь - функції асинхронізації передають свої викинуті помилки на що охоплює область, де вони обробляються так само, як і локальні помилки для цієї області. Це один з головних переваг асинхронізації / очікування!
— RiqueW

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