【问题标题】:try/catch blocks with async/await使用 async/await 尝试/捕获块
【发布时间】:2017-04-14 12:39:48
【问题描述】:

我正在深入研究 node 7 的 async/await 功能,并不断遇到这样的代码

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

这似乎是使用 async/await 解决/拒绝或返回/抛出的唯一可能性,但是,v8 没有优化 try/catch 块中的代码?!

有其他选择吗?

【问题讨论】:

  • “等待不成功后抛出”是什么意思?如果出错?如果它没有返回预期的结果?你可以在 catch 块中重新抛出。
  • afaik v8 优化 try/catch,一个 throw 语句是慢的
  • 我还是不明白这个问题。你范使用旧的承诺链,但我不认为它会更快。所以你关心try-catch的性能?那跟 async await 有什么关系呢?
  • 检查我的答案我试图获得更清洁的方法
  • 这里你可以这样做stackoverflow.com/a/61833084/6482248它看起来更干净

标签: node.js async-await ecmascript-2017


【解决方案1】:

替代方案

替代方案:

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,如果promise 已解决,则打印它,或者如果promise 被拒绝,则抛出异常并运行打印错误的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.js 中的类似事情。如果这样的东西被大量使用,那么它最终会被 V8 或 SpiderMonkey 或 Chakra 团队优化,每个人都会效仿——这并不是作为原则没有优化,只是没有问题。

即使它没有经过优化,我仍然会争辩说,如果你在 Node 中最大化你的 CPU,那么你可能应该用 C 编写你的数字运算——这就是本机插件的用途,等等。或者像 node.native 这样的东西可能比 Node.js 更适合这项工作。

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

【讨论】:

  • 我知道代码可以很容易地用 Promises 编写,如上所述,我已经在各种示例中看到它,这就是我问的原因。在 try/catch 中进行单个操作可能不是问题,但具有更多应用程序逻辑的多个 async/await 函数可能是问题。
  • @Patrick “可能是”和“将是”是推测和实际测试之间的区别。我针对单个语句对其进行了测试,因为这就是您的问题,但是您可以轻松地将我的示例转换为测试多个语句。我还提供了其他几个选项来编写您也询问过的异步代码。如果它回答了您的问题,那么您可以考虑accepting the answer。总结一下:当然异常比返回慢,但它们的使用应该是异常。
  • 抛出异常确实应该是异常。话虽如此,无论您是否抛出异常,代码都未优化。性能下降来自使用try catch,而不是抛出异常。虽然数字很小,但根据您的测试,它几乎慢了 10 倍,这并非微不足道。
【解决方案2】:

类似于 Golang 中的错误处理的替代方案

因为 async/await 在底层使用了 Promise,所以你可以像这样编写一个小实用函数:

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

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

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 你可能在重新发明轮子——已经有一个流行的包可以做到这一点:npmjs.com/package/await-to-js
  • golang 不是节点。
  • 啊,欢迎来到 StackOverflow,在提出问题 4 年后的回答中,golang is not node floats in。我认为关键是你可以在 Node 中编写一个实用函数来完成他的工作问。它可能在 Go 中,但重点很明确。
  • @DylanWright 答案甚至不是用 Go 编写的——它是 JavaScript。它只是说明这是您实现异步逻辑的方式,类似于 Go 的实现方式。
【解决方案3】:

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 相比,这种语法更简洁。

【讨论】:

  • 试过了,很喜欢。以安装新模块为代价的干净、可读的代码。但是如果你打算写很多异步函数,我得说这是一个很好的补充!谢谢
  • 你甚至不需要安装库。如果您查看它的源代码,它实际上是 1 功能。只需将该函数复制并粘贴到项目中的实用程序文件中即可。
  • 这是to 函数的单行代码:const to = promise =&gt; promise.then(res =&gt; [null, res]).catch(err =&gt; [err || true, null]);
【解决方案4】:
async function main() {
  var getQuoteError
  var quote = await getQuote().catch(err => { getQuoteError = err }

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

除了声明一个可能的 var 来在顶部保存错误之外,您还可以这样做

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 块,我觉得同样麻烦

【讨论】:

    【解决方案5】:

    更简洁的替代方案如下:

    由于每个异步函数在技术上都是一个承诺

    您可以在使用 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'))
    

    不需要try catch,因为所有的promise错误都会被处理,并且你没有代码错误,你可以在父级中省略它!!

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

    【讨论】:

    • 您有 3 个功能。一个获取值并捕获错误,另一个如果没有错误则返回,最后调用第一个函数并带有回调以检查该函数是否返回错误。所有这一切都可以通过一个“promise”.then(cb).catch(cb) 或 trycatch 块来解决。
    • @Chiefkoshi 正如您所看到的,单个 catch 不会做,因为在所有三种情况下处理的错误都不同。如果第一个失败,则返回 d(),如果第二个失败,则返回 null,如果最后一个失败,则会显示不同的错误消息。该问题要求在使用 await 时处理错误。所以这也是答案。如果任何一个失败,都应该执行。在这个不干净的特定示例中,尝试 catch 块将需要其中三个
    • 该问题不要求在承诺失败后执行。在这里等待 B,然后运行 ​​C,如果出错则返回 D。这个清洁剂怎么样? C必须等待B,但它们彼此独立。如果他们是独立的,我看不出他们为什么会在一起的理由。如果它们相互依赖,那么如果 B 失败,你会想要停止执行 C,.then.catch 或 try-catch 的工作。我假设它们什么都不返回并执行一些与 A 完全无关的异步操作。为什么用 async await 调用它们?
    • 问题是关于在使用 async/await 时尝试 catch 块来处理错误的替代方法。这里的例子是描述性的,只是一个例子。它以顺序方式显示了对独立操作的单独处理,这通常是使用 async/await 的方式。为什么用 async await 调用它们,只是为了说明如何处理它。它的描述性不仅仅是合理的。
    【解决方案6】:

    我想这样做:)

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

    【讨论】:

    • 代码不是很清楚,不过看起来很有趣,你能编辑一下吗?
    • 不幸的是,这个答案中没有解释,因为它确实展示了一种很好的方法来避免尝试捕获您使用 await 分配的每个 const!
    【解决方案7】:

    我认为,一个简单且解释清楚的示例来自 MDN DOCSMaking asynchronous programming easier with async and await

    作为一个例子,他们使用API Fetch,然后是 2 种类型,一种是普通类型,另一种是 hybrid,其中 async 和 Promise 混合在一起。

    1. 简单示例

       async function myFetch() {
         let response = await fetch('coffee.jpg');
         // Added manually a validation and throws an error
         if (!response.ok) {
           throw new Error(`HTTP error! status: ${response.status}`);
         }
      
         let myBlob = await response.blob();
      
         let objectURL = URL.createObjectURL(myBlob);
         let image = document.createElement('img');
         image.src = objectURL;
         document.body.appendChild(image);
       }
      
       myFetch()
       .catch(e => { // Catches the errors...
         console.log('There has been a problem with your fetch operation: ' + e.message);
       });
      
    2. 混合方法

    由于async 关键字将函数转换为promise,您可以重构代码以使用promises 和await 的混合方法,从而带来函数的后半部分放到一个新块中以使其更灵活:

        async function myFetch() { // Uses async
          let response = await fetch('coffee.jpg');
          // Added manually a validation and throws an error
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          return await response.blob();
    
        }
    
        myFetch().then((blob) => { // uses plain promise
          let objectURL = URL.createObjectURL(blob);
          let image = document.createElement('img');
          image.src = objectURL;
          document.body.appendChild(image);
        }).catch(e => console.log(e));
    

    Adding error handling

    1. 正常 异步函数 myFetch() { 尝试 { let response = await fetch('coffee.jpg');

         if (!response.ok) {
           throw new Error(`HTTP error! status: ${response.status}`);
         }
         let myBlob = await response.blob();
         let objectURL = URL.createObjectURL(myBlob);
         let image = document.createElement('img');
         image.src = objectURL;
         document.body.appendChild(image);
      
       } catch(e) {
         console.log(e);
       }
       }
      
       myFetch();
      
    2. 混合(最佳)

         async function myFetch() {
           let response = await fetch('coffee.jpg');
           if (!response.ok) {
             throw new Error(`HTTP error! status: ${response.status}`);
           }
           return await response.blob();
      
         }
      
         myFetch().then((blob) => {
           let objectURL = URL.createObjectURL(blob);
           let image = document.createElement('img');
           image.src = objectURL;
           document.body.appendChild(image);
         })
         .catch((e) => // Not need a try catch. This will catch it all already!
           console.log(e)
         );
      

    最佳解决方案

    给出的最佳解决方案是遵循这些原则但更清晰的是这个答案 --> StackOverflow: try/catch blocks with async/await 我相信。这里

        function promiseHandle(promise) {
          return promise
                  .then(data => [null, data])
                  .catch(err => [err]);
        }
        
        
        
        async function asyncFunc(param1, param2) {
            const [err, data] = await promiseHandle(expensiveFunction(param1, param2));
            // This just to show, that in this way we can control what is going on..
            if (err || !data) {
                if (err) return Promise.reject(`Error but not data..`);
                return Promise.reject(`Error but not data..`);
            }
            return Promise.resolve(data);
        
        }
    

    【讨论】:

      【解决方案8】:

      catch@987654321 根据我的经验,这种方式是危险的。整个堆栈中抛出的任何错误都将被捕获,而不仅仅是来自这个 Promise 的错误(这可能不是您想要的)。

      promise 的第二个参数已经是一个拒绝/失败回调。改为使用它会更好、更安全。

      这是我为处理此问题而编写的 typescript typesafe one-liner:

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

      【讨论】:

        【解决方案9】:

        不需要像 await-to-js 这样的库,to-function 的简单单行符(也显示在其他答案中)就可以了:

        const to = promise => promise.then(res => [null, res]).catch(err => [err || true, null]);
        

        用法:

        async function main()
        {
            var [err, quote] = await to(getQuote());
            if(err)
            {
                console.log('warn: Could not get quote.');
            }
            else
            {
                console.log(quote);
            }
        }
        

        但是,如果错误导致函数或程序终止,例如:

        async function main()
        {
            var [err, quote] = await to(getQuote());
            if(err) return console.error(err);
            console.log(quote);
        }
        

        那么你也可以简单地让错误从 main() 自动返回,这无论如何都是异常的预期目的:

        async function main()
        {
            var quote = await getQuote();
            console.log(quote);
        }
        
        main().catch(err => console.error('error in main():', err));
        

        抛出错误与返回错误

        如果您需要处理预期会发生的错误,那么使用throwreject 是不好的做法。相反,让getQuote() 函数始终使用以下任何一种进行解析:

        • resolve([err, result])
        • resolve(null)
        • resolve(new Error(...))
        • resolve({error: new Error(), result: null})

        抛出错误(或异步中的等价物:拒绝承诺)必须保持异常。由于异常仅在事情向南时发生,并且在正常使用期间不应发生,因此优化不是优先事项。因此,异常的唯一后果可能是函数终止,如果没有被捕获,这是默认行为。

        除非您处理设计不佳的第 3 方库,或者您将第 3 方库函数用于非预期用例,否则您可能使用to-函数.

        【讨论】:

          【解决方案10】:

          在Express框架的情况下,我一般遵循以下方法。我们可以创建一个函数来解决一个承诺。比如catchAsync函数:

          const catchAsync = (fn) => (req, res, next) =>{
              Promise.resolve(fn(req, res, next)).catch((err) => next(err));
          });
          

          这个函数可以在任何我们需要try/catch的地方调用。它接受我们调用的函数,并根据正在执行的动作解析或拒绝它。我们可以这样称呼它

          const sampleFunction = catchAsync(async (req, res) => {
                     const awaitedResponse = await getResponse();
                     res.send(awaitedResponse);
          });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-23
            相关资源
            最近更新 更多