Як відхилити синтаксис 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.