191

我正在深入研究 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 块中的代码?!

有替代品吗?

4

10 回答 10

220

备择方案

对此的替代方法:

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()给出,作为异常抛出:1f2()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 更适合这项工作。

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

于 2016-11-30T11:04:48.063 回答
36

类似于 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
  }
}
于 2018-03-16T01:37:28.663 回答
28

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

于 2018-01-15T12:39:29.890 回答
21
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 块,我觉得同样麻烦

于 2018-01-30T17:37:09.190 回答
16

更清洁的替代方案如下:

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

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

于 2019-06-13T12:50:54.383 回答
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);
  });
};
于 2018-03-12T09:01:51.867 回答
2

我认为,一个简单且解释清楚的示例来自使用MDN DOCSasync 和 await 使异步编程更容易

例如,他们使用API Fetch和 2 种类型,一种是普通的,另一种是混合的,其中 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));

添加错误处理

  1. 普通异步函数 myFetch() { try { 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);
    
    }
于 2021-12-11T20:15:59.357 回答
1

catch以我的经验,以这种方式进行是很危险的。整个堆栈中抛出的任何错误都将被捕获,而不仅仅是来自这个 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()
);
于 2019-06-26T07:30:32.047 回答
0

不需要像 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));

抛出错误与返回错误

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

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

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

除非您处理设计不佳的 3rd-party 库,或者您将 3rd-party library 函数用于非预期用例,否则您可能应该使用to-function。

于 2021-11-14T12:52:53.240 回答
0

在 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);
});
于 2021-11-17T09:56:04.390 回答