如何拒绝异步/等待语法?


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; }); }
Bergi

10
我认为将这个问题中的代码转换为Vanilla JS会有帮助,因为这个问题与TypeScript无关。如果这样做的话,该编辑可能会被接受吗?
雅各布·福特

Answers:


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);,但是同样,没有上下文信息。)

(在您的情况下,当您使用TypeScriptfooretrn值为时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
工会联合会'17

2
是的,但是,如果您的代码链确实在使用异步/等待,那么......在这里很难键入,让我作为答案进行演示
工会联合会,2017年

1
您有什么理由要抛出一个新错误而不是catch块中给您的错误?
阿德里安M

1
@sebastian-我不明白你的意思。在async函数中,没有resolvereject函数。有returnthrow,这是惯用的方法来解决,拒绝async功能的承诺。
TJ Crowder

1
@ Jan-PhilipGehrcke- 可以,但是我从不这样做。它正在创建一个实例,new使其明确。另请注意,如果您有Error子类(class MyError extends Error),则不能忽略它,所以...
TJ Crowder

146

可能还应该提到,您可以catch()在调用异步操作之后简单地链接一个函数,因为在后台仍然返回了一个Promise。

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

这样,您可以避免try/catch不喜欢的语法。


1
因此,如果我想拒绝async函数,则抛出异常,然后很好地捕获它,.catch()就像返回Promise.reject或调用一样reject。我喜欢!
icl7126 '18

7
我不明白为什么这应该是公认的答案。不仅可以使答案更干净,而且还可以await在一个例行程序中处理所有可能的故障。除非每个案例都需要非常具体的案例,否则await我不明白为什么要像这样捕获它们。我只是拙见。
edgaralienfoe

1
在我的用例中,@ jablesauce不仅需要分别捕获每个await失败,而且还需要使用基于Promise的框架,该框架拒绝了对错误的承诺。
鲁汶·卡拉西克

它对我不起作用。如果url失败,似乎不会进入catch块。[响应] =等待oauthGet( ${host}/user/permissions/repositories_wrong_url/,accessToken,accessTokenSecret).catch(err => {logger.error('无法获取存储库权限',err); callback(err);})
sn.anurag,

1
await这里不需要关键字。
Ashish Rawat,

12

您可以创建一个包装函数,该函数接受一个promise,如果没有错误,则返回一个包含数据的数组,如果有错误,则返回错误。

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

编写异步函数的更好方法是从头开始返回一个未决的Promise,然后在Promise的回调中处理拒绝和解决方案,而不是随便吐出错误时被拒绝的Promise。例:

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
        }
    });
}

然后,您只需在返回的Promise上链接方法即可:

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

bar()

来源-本教程:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise


5
该问题专门询问有关使用异步/等待的问题。不使用诺言

该答案并非最终的正确答案。这是对上面给出的其他答案的支持性答案。我会把它记为注释,但考虑到我有代码,答案字段是一个更好的地方。
OzzyTheGiant

感谢您的澄清。展示如何制作异步功能绝对有帮助。更新第二个代码块以使用await将更加相关和有用。干杯

我已编辑您的回复以进行更新。让我知道我是否错过任何事情
Mak

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]);
}

在以下链接中将版权归Dima Grossman所有。


1
我几乎完全(更干净)地使用了这种构造,并且有一个“ to”模块已经存在了一段时间了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
另外,请记住,如果对该函数的调用位于封闭范围内的try / catch块内,则无需对处理该异步函数内的意外错误感到压力,因为与Promises不同,异步函数会将其抛出的错误冒泡到封闭范围,在该范围内处理它们就像该范围的本地错误一样。这是异步/等待的主要好处之一!
RiqueW '18 -10-31

微基准测试是魔鬼。仔细看数字。您需要做1000倍的操作才能注意到这里有1毫秒的差异。是的,添加throw / catch会优化功能。但是a)如果您正在等待异步,则可能需要几个数量级才能在后台花费比0.0005毫秒更长的时间。b)您需要将其做1000倍才能在此处产生1毫秒的差异。
Jamie Pate
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.