thử / bắt các khối với async / await


116

Tôi đang đào sâu vào tính năng async / await của nút 7 và tiếp tục tìm hiểu mã như thế này

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

Đây dường như là khả năng duy nhất giải quyết / từ chối hoặc trả lại / ném với async / await, tuy nhiên, v8 không tối ưu hóa mã trong các khối thử / bắt?!

Có những lựa chọn thay thế?


"Ném sau khi chờ đợi không thành công" nghĩa là gì? Nếu nó có lỗi? Nếu nó không trả lại kết quả mong đợi? Bạn có thể nghĩ lại trong khối bắt.
DevDig

afaik v8 làm tối ưu hóa thử / bắt, một tuyên bố ném là chậm
Tamas Hegedus

1
Tôi vẫn không hiểu câu hỏi. Bạn van sử dụng chuỗi lời hứa cũ, nhưng tôi không nghĩ nó sẽ nhanh hơn. Vì vậy, bạn quan tâm đến hiệu suất của thử bắt? Vậy thì nó sẽ làm gì với async đang chờ?
Tamas Hegedus

Kiểm tra câu trả lời của tôi Tôi đã cố gắng để có được một cách tiếp cận sạch hơn
zardilior

Tại đây bạn có thể thực hiện stackoverflow.com/a/61833084/6482248 Nó trông sạch hơn
Prathamesh

Câu trả lời:


133

Lựa chọn thay thế

Một thay thế cho điều này:

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

sẽ là một cái gì đó như thế này, sử dụng lời hứa rõ ràng:

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

hoặc một cái gì đó như thế này, sử dụng phong cách tiếp tục đi qua:

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

Ví dụ ban đầu

Những gì mã ban đầu của bạn làm là tạm dừng việc thực thi và chờ đợi lời hứa được trả về getQuote(). Sau đó, nó tiếp tục thực hiện và ghi giá trị được trả về var quotevà sau đó in nó nếu lời hứa đã được giải quyết hoặc ném một ngoại lệ và chạy khối lệnh bắt lỗi in nếu lời hứa bị từ chối.

Bạn có thể làm điều tương tự bằng cách sử dụng API Promise trực tiếp như trong ví dụ thứ hai.

Hiệu suất

Bây giờ, cho hiệu suất. Hãy thử nghiệm nó!

Tôi vừa viết mã này - f1()đưa ra 1như một giá trị trả về, f2()ném 1như một ngoại lệ:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

Bây giờ, hãy gọi cùng một mã triệu lần, trước tiên bằng f1():

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

Và sau đó hãy thay đổi f1()thành f2():

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

Đây là kết quả tôi nhận được f1:

$ time node throw-test.js 
1000000

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

Đây là những gì tôi nhận được f2:

$ time node throw-test.js 
1000000

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

Có vẻ như bạn có thể làm một cái gì đó như 2 triệu ném một giây trong một quy trình đơn luồng. Nếu bạn đang làm nhiều hơn thế thì bạn có thể cần phải lo lắng về nó.

Tóm lược

Tôi sẽ không lo lắng về những điều như thế trong Node. Nếu những thứ như thế được sử dụng nhiều thì cuối cùng nó sẽ được tối ưu hóa bởi các đội V8 hoặc SpiderMonkey hoặc Chakra và mọi người sẽ tuân theo - không giống như nó không được tối ưu hóa theo nguyên tắc, đó không phải là vấn đề.

Ngay cả khi nó không được tối ưu hóa thì tôi vẫn lập luận rằng nếu bạn tối đa hóa CPU của mình trong Node thì có lẽ bạn nên viết số của bạn đang giòn trong C - đó là những gì các addon gốc dành cho, trong số những thứ khác. Hoặc có thể những thứ như node.native sẽ phù hợp với công việc hơn Node.js.

Tôi đang tự hỏi điều gì sẽ là một trường hợp sử dụng cần ném quá nhiều ngoại lệ. Thông thường, ném một ngoại lệ thay vì trả về một giá trị là một ngoại lệ.


Tôi biết mã có thể dễ dàng được viết bằng Promise, như đã đề cập, tôi đã thấy nó xung quanh trên các ví dụ khác nhau, đó là lý do tại sao tôi hỏi. Có một hoạt động trong thử / bắt có thể không thành vấn đề, nhưng nhiều chức năng không đồng bộ / chờ đợi với logic ứng dụng tiếp theo có thể.
Patrick

4
@Patrick "có thể" và "sẽ là" là một sự khác biệt giữa đầu cơ và thử nghiệm thực sự. Tôi đã thử nghiệm nó cho một câu lệnh vì đó là những gì trong câu hỏi của bạn nhưng bạn có thể dễ dàng chuyển đổi các ví dụ của tôi để kiểm tra nhiều câu lệnh. Tôi cũng cung cấp một số tùy chọn khác để viết mã không đồng bộ mà bạn cũng đã hỏi về. Nếu nó trả lời câu hỏi của bạn thì bạn có thể xem xét chấp nhận câu trả lời . Tóm lại: tất nhiên các ngoại lệ chậm hơn lợi nhuận nhưng việc sử dụng chúng phải là một ngoại lệ.
rsp

1
Ném một ngoại lệ thực sự được coi là một ngoại lệ. Điều đó đang được nói, mã không được đánh giá cao cho dù bạn có ném ngoại lệ hay không. Các hit hiệu suất đến từ việc sử dụng try catch, không phải từ một ngoại lệ. Mặc dù các con số rất nhỏ, nhưng nó chậm hơn gần 10 lần theo các thử nghiệm của bạn, điều này không đáng kể.
Nepoxx

21

Một thay thế cho khối try-Catch là lib -to-js lib. Tôi thường sử dụng nó. Ví dụ:

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

}

Cú pháp này sạch hơn nhiều khi so sánh với thử bắt.


Đã thử nó và yêu nó. Làm sạch và mã có thể đọc được với chi phí cài đặt một mô-đun mới. Nhưng nếu bạn đang dự định viết nhiều hàm async, tôi phải nói rằng đây là một bổ sung tuyệt vời! Cảm ơn
filipbarak

21

Thay thế tương tự như xử lý lỗi trong Golang

Vì async / await sử dụng lời hứa dưới mui xe, bạn có thể viết một chức năng tiện ích nhỏ như thế này:

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

Sau đó nhập nó bất cứ khi nào bạn cần để bắt một số lỗi và bọc chức năng async của bạn sẽ trả lại lời hứa với nó.

import catchEm from 'utility';

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

Tôi đã tạo một gói NPM thực hiện chính xác như trên - npmjs.com/package/@simmo/task
Mike

2
@Mike Bạn có thể đang phát minh lại bánh xe - đã có một gói phổ biến thực hiện chính xác điều này: npmjs.com/package/await-to-js
Jakub Kukul

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

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

Thay vào đó, thay vì khai báo một var có thể có lỗi ở đầu bạn có thể làm

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

Mặc dù điều đó sẽ không hoạt động nếu một cái gì đó như lỗi TypeError hoặc Reference được ném ra. Bạn có thể đảm bảo đó là một lỗi thường xuyên mặc dù với

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

Sở thích của tôi cho việc này là gói mọi thứ trong một khối thử lớn, trong đó có nhiều lời hứa được tạo ra có thể khiến cho việc xử lý lỗi cụ thể đối với lời hứa đã tạo ra nó trở nên khó khăn. Với sự thay thế là nhiều khối thử bắt mà tôi thấy cồng kềnh không kém


8

Một thay thế sạch hơn sẽ là như sau:

Do thực tế là mọi chức năng không đồng bộ về mặt kỹ thuật là một lời hứa

Bạn có thể thêm sản phẩm khai thác vào chức năng khi gọi chúng với sự chờ đợi

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'))

Không cần thử bắt, vì tất cả các lỗi hứa hẹn đều được xử lý và bạn không có lỗi mã, bạn có thể bỏ qua lỗi đó trong phần gốc !!

Hãy nói rằng bạn đang làm việc với mongodb, nếu có lỗi, bạn có thể thích xử lý nó trong hàm gọi nó hơn là tạo các hàm bao hoặc sử dụng các hàm bắt.


Bạn có 3 chức năng. Một nhận giá trị và bắt lỗi, một số khác bạn trả về nếu không có lỗi và cuối cùng là một cuộc gọi đến hàm đầu tiên với một cuộc gọi lại để kiểm tra xem cái đó có trả về lỗi không. Tất cả điều này được giải quyết bằng một "lời hứa" .then (cb) .catch (cb) hoặc khối trycatch.
Trưởng koshi

@Chiefkoshi Như bạn có thể thấy một lần bắt sẽ không xảy ra vì lỗi đang được xử lý khác nhau trong cả ba trường hợp. Nếu cái đầu tiên thất bại, nó sẽ trả về d (), nếu cái thứ hai không thành công, nó sẽ trả về null nếu cái cuối cùng không xuất hiện một thông báo lỗi khác. Câu hỏi yêu cầu xử lý lỗi khi sử dụng đang chờ. Vì vậy, đó cũng là câu trả lời. Tất cả nên thực hiện nếu có một thất bại. Hãy thử các khối bắt sẽ yêu cầu ba trong số chúng trong ví dụ cụ thể không sạch hơn
zardilior

1
Câu hỏi không yêu cầu thực hiện sau khi thất hứa. Ở đây bạn đợi B, sau đó chạy C và trả về D nếu chúng bị lỗi. Làm thế nào là sạch hơn? C phải đợi B nhưng họ độc lập với nhau. Tôi không thấy lý do tại sao họ sẽ ở A cùng nhau nếu họ độc lập. Nếu chúng phụ thuộc vào nhau, bạn muốn dừng thực thi C nếu B thất bại, công việc của .then.catch hoặc thử bắt. Tôi cho rằng họ không trả lại gì và thực hiện một số hành động không đồng bộ hoàn toàn không liên quan đến A. Tại sao họ được gọi với async đang chờ?
Trưởng koshi

Câu hỏi liên quan đến các lựa chọn thay thế để thử các khối bắt để xử lý lỗi khi sử dụng async / await. Ví dụ ở đây là để mô tả và không có gì ngoài một ví dụ. Nó cho thấy việc xử lý riêng lẻ các hoạt động độc lập theo cách tuần tự, thường là cách sử dụng async / await. Tại sao chúng được gọi với async đang chờ, chỉ là để cho thấy nó có thể được xử lý như thế nào. Nó mô tả nhiều hơn hợp lý.
zardilior

2

Tôi muốn làm theo cách này :)

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

Nó tương tự như xử lý lỗi với co

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

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

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

Mã không phải là người đàn ông rất rõ ràng, mặc dù trông thú vị, bạn có thể chỉnh sửa?
zardilior

Thật không may là không có lời giải thích nào trong câu trả lời này bởi vì nó thực sự chứng minh một cách tuyệt vời để tránh bắt thử mọi const bạn giao cho await!
Jim

0

catchTheo cách này, theo kinh nghiệm của tôi, là nguy hiểm. Bất kỳ lỗi nào được ném trong toàn bộ ngăn xếp sẽ bị bắt, không chỉ là lỗi từ lời hứa này (có thể không phải là điều bạn muốn).

Đối số thứ hai cho một lời hứa đã là một cuộc gọi lại từ chối / thất bại. Thay vào đó, tốt hơn và an toàn hơn khi sử dụng nó.

Đây là một kiểu chữ an toàn một lớp tôi đã viết để xử lý việc này:

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()
);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.