使用异步/等待尝试/捕获块


116

我正在研究节点7异步/等待功能,并不断跨这样的代码绊脚

function getQuote() {
  let quote = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  return quote;
}

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

main();

这似乎是使用异步/等待来解决/拒绝或返回/抛出的唯一可能性,但是,v8不会在try / catch块中优化代码吗?

有其他选择吗?


“等待后扔不成功”是什么意思?如果出错?如果没有返回预期的结果?您可以在catch块中重新抛出。
DevDig '16

afaik v8可以优化try / catch,throw语句很慢
Tamas Hegedus

1
我仍然不明白这个问题。您可以使用旧的Promise链,但我认为这样不会更快。所以您担心try-catch的性能吗?那么异步等待有什么关系呢?
塔玛斯·希格杜斯

检查我的答案我试图获得一种更清洁的方法
zardilior

在这里您可以执行此stackoverflow.com/a/61833084/6482248看起来更干净
Prathamesh更多

Answers:


133

备择方案

替代方法:

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

显式地使用诺言将是这样的:

function main() {
  getQuote().then((quote) => {
    console.log(quote);
  }).catch((error) => {
    console.error(error);
  });
}

或类似的东西,使用延续传递样式:

function main() {
  getQuote((error, quote) => {
    if (error) {
      console.error(error);
    } else {
      console.log(quote);
    }
  });
}

原始例子

您原始代码的作用是暂停执行,然后等待返回的诺言getQuote()解决。然后,它继续执行,并将返回的值写入var quote,如果承诺已解决,则将其打印出来;如果承诺被拒绝,则抛出异常并运行catch块,打印出错误。

您可以像第二个示例一样直接使用Promise API进行相同的操作。

性能

现在,为了表现。让我们测试一下!

我刚才写的代码- f1()1作为返回值,f2()抛出1一个例外:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

现在,我们先用一百万次调用相同的代码f1()

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f1();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

然后让我们更改f1()f2()

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f2();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

这是我得到的结果f1

$ time node throw-test.js 
1000000

real    0m0.073s
user    0m0.070s
sys     0m0.004s

这是我得到的f2

$ time node throw-test.js 
1000000

real    0m0.632s
user    0m0.629s
sys     0m0.004s

似乎您可以在一个单线程进程中每秒执行200万次抛出。如果您要做的还不止这些,那么您可能需要担心。

摘要

我不用担心Node中的问题。如果像这样的事情得到了广泛使用,那么它最终将由V8或SpiderMonkey或Chakra团队进行优化,并且每个人都会效仿-这并不是说它没有作为一个原则进行优化,这不是问题。

即使未进行优化,我仍然会争辩说,如果您要在Node中最大化CPU,那么您可能应该使用C语言编写数字运算-这就是本机插件的用途。或者也许像node.native这样的事情比Node.js更适合这份工作。

我想知道什么是用例,需要抛出这么多异常。通常,抛出异常而不是返回值是一个异常。


我知道代码可以很容易地用Promises编写,如上所述,我在各种示例中都看到了它,这就是我要问的原因。在try / catch中进行单个操作可能不是问题,但是可能需要具有更多应用程序逻辑的多个async / await函数。
帕特里克

4
@Patrick“可能会”和“将要”是猜测和实际测试之间的区别。我针对单个语句进行了测试,因为这就是您的问题,但是您可以轻松地将我的示例转换为针对多个语句进行测试。我还提供了一些其他选项来编写您还询问过的异步代码。如果它回答了您的问题,那么您可以考虑接受答案。总结一下:当然,异常比返回慢,但是异常的使用应该是一个例外。
rsp

1
引发异常确实应该是一个异常。话虽如此,无论您是否抛出异常,代码都没有经过优化。性能下降源于使用try catch,而不是引发异常。虽然这个数字很小,但根据您的测试,它几乎慢了10倍,这并不重要。
Nepoxx

21

与Golang中的错误处理类似的替代方法

因为async / await在幕后使用promises,所以您可以编写一个小的实用程序函数,如下所示:

export function catchEm(promise) {
  return promise.then(data => [null, data])
    .catch(err => [err]);
}

然后,在需要捕获一些错误时将其导入,并包装异步函数,该函数将返回一个Promise。

import catchEm from 'utility';

async performAsyncWork() {
  const [err, data] = await catchEm(asyncFunction(arg1, arg2));
  if (err) {
    // handle errors
  } else {
    // use data
  }
}

我创建了一个NPM软件包,可以完成上述工作-npmjs.com/package/@simmo/task
Mike

2
@Mike您可能正在重新发明轮子-已经有一个受欢迎的软件包完全可以做到这一点:npmjs.com/package/await-to-js
Jakub Kukul

21

try-catch块的替代方法是await-to-js lib。我经常使用它。例如:

import to from 'await-to-js';

async function main(callback) {
    const [err,quote] = await to(getQuote());
    
    if(err || !quote) return callback(new Error('No Quote found'));

    callback(null,quote);

}

与try-catch相比,此语法更加简洁。


尝试过并喜欢它。干净易读的代码,但要以安装新模块为代价。但是,如果您打算编写许多异步函数,我必须说这是一个很好的补充!谢谢
filipbarak

15
async function main() {
  var getQuoteError
  var quote = await getQuote().catch(err => { getQuoteError = err }

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

另外,您也可以声明一个变量而不是在顶部声明错误,

if (quote instanceof Error) {
  // ...
}

如果抛出TypeError或Reference错误之类的东西,那将无法正常工作。您可以通过以下方式确保它是常规错误

async function main() {
  var quote = await getQuote().catch(err => {
    console.error(err)      

    return new Error('Error getting quote')
  })

  if (quote instanceOf Error) return quote // get out of here or do whatever

  console.log(quote)
}

我的偏好是将所有内容包装在一个大的try-catch块中,在该块中创建了多个promise,这使得处理错误(特别是针对创建它的promise)变得很麻烦。另一种选择是多个try-catch块,我觉得同样麻烦


8

更加干净的替代方法如下:

由于每个异步功能在技术上都是一个承诺

您可以在通过await调用函数时将捕获添加到函数中

async function a(){
    let error;

    // log the error on the parent
    await b().catch((err)=>console.log('b.failed'))

    // change an error variable
    await c().catch((err)=>{error=true; console.log(err)})

    // return whatever you want
    return error ? d() : null;
}
a().catch(()=>console.log('main program failed'))

无需尝试捕获,因为所有承诺的错误都已处理,并且您没有代码错误,因此可以在父级中忽略它!

假设您正在使用mongodb,如果发生错误,您可能更喜欢在调用它的函数中处理它,而不是制作包装器或使用try catch。


您有3个功能。一个获取值并捕获错误,另一个返回值(如果没有错误),最后返回带有回调的第一个函数,以检查该函数是否返回了错误。所有这些都可以通过一个“ promise” .then(cb).catch(cb)或trycatch块来解决。
酋长酋长

@Chiefkoshi正如您所看到的,因为在所有三种情况下错误处理方式都不相同,所以单个捕获不会起作用。如果第一个失败,则返回d();如果第二个失败,则返回null;如果最后一个失败,则显示不同的错误消息。该问题要求使用await时处理错误。所以这也是答案。如果任何一个失败,则应全部执行。在这个特定示例中,尝试捕获块将需要其中三个,这并不干净
zardilior,

1
问题不要求在失败的承诺后执行。在这里,您等待B,然后运行C,如果它们出错则返回D。这个清洁剂怎么样?C必须等待B,但它们彼此独立。如果他们是独立的,我不明白为什么他们会在一起。如果它们相互依赖,则您希望在B失败时停止执行C,执行.then.catch或try-catch的工作。我假设它们什么也不返回,并且执行一些与A完全无关的异步动作。为什么用async等待来调用它们?
首席志

问题是有关使用异步/等待时尝试捕获块来处理错误的替代方法。这里的例子是描述性的,仅是一个例子。它显示了按顺序方式对独立操作的单独处理,通常是异步/等待方式。为什么用async await来调用它们,只是为了说明如何处理它。它的描述意义十足。
zardilior

2

我想这样:)

const sthError = () => Promise.reject('sth error');

const test = opts => {
  return (async () => {

    // do sth
    await sthError();
    return 'ok';

  })().catch(err => {
    console.error(err); // error will be catched there 
  });
};

test().then(ret => {
  console.log(ret);
});

这类似于处理错误 co

const test = opts => {
  return co(function*() {

    // do sth
    yield sthError();
    return 'ok';

  }).catch(err => {
    console.error(err);
  });
};

代码不是很清楚,但是看起来很有趣,您可以编辑吗?
zardilior19年

不幸的是,此答​​案中没有任何解释,因为它确实展示了一种避免尝试捕获您分配给每个const的好方法await
Jim Jim

0

catch以我的经验,以这种方式进行是危险的。将捕获整个堆栈中抛出的所有错误,而不仅仅是此承诺中的错误(可能不是您想要的)。

Promise的第二个参数已经是拒绝/失败回调。改用它会更好,更安全。

这是我为处理此问题而写的打字稿类型安全单线:

function wait<R, E>(promise: Promise<R>): [R | null, E | null] {
  return (promise.then((data: R) => [data, null], (err: E) => [null, err]) as any) as [R, E];
}

// Usage
const [currUser, currUserError] = await wait<GetCurrentUser_user, GetCurrentUser_errors>(
  apiClient.getCurrentUser()
);
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.