如何在async/await语法中使用reject?

471

我可以如何拒绝由async/await函数返回的Promise?

例如,原本:

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

那么,在这种情况下,我应该如何正确地拒绝这个承诺呢?

22
避免使用Promise构造反模式!即使是第一个片段也应该这样写: foo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); } - Bergi
16
我认为将这个问题中的代码翻译成纯JS会很有帮助,因为这个问题与TypeScript无关。如果我这样做,这个编辑是否会被接受? - Jacob Ford
2
我认为类型有助于使它更加易于理解 - 您可以明确知道每个实例返回的是什么。 - nycynik
8个回答

509

您的最佳选择是使用包装该值的 Error 抛出异常,这将导致被包装在 Error 中的拒绝承诺:

throw an Error wrapping the value, which results in a rejected promise with an Error wrapping the value:

} catch (error) {
    throw new Error(400);
}

您也可以直接抛出该值,但这样就没有堆栈跟踪信息:

} catch (error) {
    throw 400;
}

或者,返回一个拒绝的Promise,其中包装了该值的Error,但这不是惯用方式:

} catch (error) {
    return Promise.reject(new Error(400));
}
< p >(或者只需< code>return Promise.reject(400);,但是这样就没有上下文信息了。)

在您的情况下,由于您正在使用TypeScript并且foo的返回值是Promise<A>,因此您将使用以下代码:

return Promise.reject<A>(400 /*or Error*/ );

async/await的情况下,后者可能有点语义不匹配,但它确实有效。

如果你抛出一个Error,那么与使用await语法消耗你的foo结果的任何东西配合得很好:

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`.
}

14
由于async/await是将异步流程转换为同步语法,我的看法是使用throwPromise.reject()更好。是否应该抛出throw 400是另一个问题。在原始贴中,它拒绝了400,我们可以认为它应该抛出一个错误而不是拒绝。 - unional
2
是的,然而,如果你的代码链确实使用了async/await,那么你将会......在这里打字太麻烦了,让我用答案演示一下。 - unional
1
你有没有任何理由想要抛出一个新的错误而不是在 catch 块中捕获到的错误? - Adrian M
1
@sebastian - 我不知道你在那里指的是什么。在async函数中,没有resolvereject函数。有returnthrow,它们是解决和拒绝async函数承诺的惯用方式。 - T.J. Crowder
1
@Jan-PhilipGehrcke - 你可以,但我从来不这样做。它会创建一个实例,new使其明确。还要注意,如果你有一个Error子类(class MyError extends Error),你不能省略它,所以... - T.J. Crowder
显示剩余3条评论

205

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

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

如果您不喜欢 try/catch 语法,可以通过这种方式避免使用它。


2
所以,如果我想要拒绝我的async函数,我会抛出异常,然后使用.catch()捕获它,就像我返回Promise.reject或调用reject一样。我喜欢它! - icl7126
10
我不明白为什么这个答案应该被接受。接受的答案不仅更简洁,而且还处理了所有可能的await故障。除非每个await需要非常特定的情况,否则我不明白为什么你要像这样捕获它们。这只是我的个人意见。 - mesllo
2
对于我的使用情况,我不仅需要单独捕获每个 await 的失败,而且还需要使用基于 Promise 的框架,在出现错误时拒绝这些 promises。 - Reuven Karasik
4
我喜欢尽可能使用这个方法,但如果catch的预期行为是从外部作用域(调用foo()函数的作用域)返回,则不能使用此解决方案。在这种情况下,我被迫使用try-catch块,因为在catch的lambda函数中使用返回语句只会从lambda函数而不是外部作用域返回。 - jose
3
不正确。如果没有“await”关键字,将返回一个未决的Promise给等待的变量。而有了“await”关键字,则确保(如果Promise在没有错误的情况下被解析)返回值是已解析的Promise结果。 - shennan
显示剩余3条评论

18

你可以创建一个包装函数,该函数接受一个承诺并返回一个数组,如果没有错误,则带有数据,如果有错误,则带有错误。

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

ES7async 函数中,可以像这样使用它:


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
}

4
看起来是想要拥有 Go 语法的可爱之处,但并没有太多优雅的东西。我发现使用它的代码足够混淆,以至于削弱了解决方案的价值。 - Kim

14

更好的编写异步函数的方法是从一开始就返回一个挂起的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


7
好的,我会尽力进行翻译。以下是您需要翻译的内容:The question specifically asked about using async/await. Not using promises - Mak
这个答案并不是旨在成为最终正确的答案。这只是对上面给出的其他答案的支持性回答。我本来会将其作为评论发布,但考虑到我有代码,回答区更合适。 - OzzyTheGiant
感谢澄清。展示如何创建异步函数肯定是有帮助的。更新第二个代码块以使用await将更加相关和有用。干杯 - Mak
我已经编辑了您的回复以进行更新。如果我漏掉了什么,请告诉我。 - Mak

8

这不是对@T.J.Crowder的回答。仅是对评论“实际上,如果异常将被转换为拒绝,我不确定是否真的介意它是错误。我仅使用Error抛出的原因可能不适用。”做出响应的评论。

如果您的代码使用async/await,则使用Error而不是400来拒绝仍然是一个好习惯:

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

6

我有一个建议,可以以新颖的方式正确处理拒绝,而不需要多个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
我几乎完全使用这种结构(更加简洁),而且有一个叫做 'await-to-js' 的模块已经存在了一段时间。不需要单独的声明,只需在解构分配前加上 let。如果只是检查错误,也可以仅使用 let [err] = - DKebler

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)”,那么我的偏好是使用自定义响应对象进行拒绝。

4
此外,请记住,如果在封闭作用域中的try/catch块中调用异步函数,则不需要为处理异步函数中未预期的错误而感到紧张,因为与Promise不同,异步函数会将其抛出的错误冒泡到封闭作用域中,在那里它们就像该作用域本地的错误一样被处理。这是使用async/await的主要好处之一! - RiqueW
1
微基准测试是魔鬼。仔细看看数字。你需要做1000倍的事情才能注意到这里的1ms差异。是的,添加throw/catch会使函数失去优化。但是a)如果您正在等待某些异步操作,它很可能需要比0.0005毫秒长得多的时间在后台发生。b)您需要做1000倍的工作才能在这里产生1ms的差异。 - Jamie Pate

0

如果你不想使用try/catch,也不关心错误信息

const f = await <PromiseHandle>.catch(console.error)

或者你可以将 Promise 函数打包,以便它始终能够解决

  function p() {
    return new Promise((resolve) => {
      if (success) {
        resolve({
          data: 'success value',
          code: 200,
          msg: null
        })
      }
      if (error) {
        // # package a layer of the same format as resolve
        resolve({
          data: null,
          code: 400,
          msg: error
        })
      }
    })
  }

这是我个人猜测的可行解决方案,如果您有更方便和可行的方法,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接