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

128

Как я могу отклонить обещание, возвращенное функцией 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.
  });
}

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

  • 14
    Избегайте Promise конструктора антипаттерна ! Даже первый фрагмент должен был быть написан foo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
  • 2
    Я думаю, что было бы полезно перевести код в этом вопросе на ванильный JS, так как вопрос не имеет ничего общего с TypeScript. Если я так и сделаю, будет ли это редактирование принято?
Теги:
asynchronous
es6-promise
ecmascript-2017

7 ответов

173
Лучший ответ

Лучше всего сделать throw a 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 значение возврата 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`.
}
  • 9
    А поскольку async / await возвращает асинхронный поток обратно к синтаксису синхронизации, throw лучше, чем Promise.reject() IMO. Стоит ли throw 400 - это другой вопрос. В OP это отклоняет 400, и мы можем утверждать, что вместо этого он должен отклонить Error .
  • 2
    Да, однако, если ваша цепочка кода действительно использует async / await, то вам будет сложно набрать здесь, позвольте мне продемонстрировать в качестве ответа
Показать ещё 5 комментариев
84

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

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

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

  • 8
    Должен быть принятый ответ.
  • 16
    Вид пропущенной точки асинхронности / ожидания.
Показать ещё 7 комментариев
6

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

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
}
  • 0
    Похоже на попытку получить прекрасный синтаксис Go, но без особой элегантности. Я нахожу код, использующий его, достаточно запутанным, чтобы высосать ценность из решения.
  • 2
    этот шаблон окаменел в этом пакете github.com/scopsy/await-to-js
4

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

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let 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

  • 3
    Вопрос специально задан по поводу использования async / await. Не используя обещания
  • 0
    Этот ответ не должен был быть окончательно правильным ответом. Это был ответ поддержки на другие ответы, приведенные выше. Я бы записал это как комментарий, но, учитывая, что у меня есть код, поле ответа - лучшее место.
Показать ещё 2 комментария
2

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

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

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

У меня есть предложение правильно обрабатывать отклонения в новом подходе, не имея нескольких блоков 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]);
}

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

0

Я знаю, что это старый вопрос, но я просто наткнулся на поток, и здесь, похоже, существует связь между ошибками и отклонением, что противоречит (во многих случаях, по крайней мере) часто повторяемого совета не использовать обработку исключений для иметь дело с ожидаемыми случаями. Чтобы проиллюстрировать: если асинхронный метод пытается аутентифицировать пользователя, и аутентификация не удалась, это отклонение (один из двух ожидаемых случаев), а не ошибка (например, если 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…) "Я думаю, что я предпочел бы отказаться от использования пользовательского объекта ответа.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню