Làm cách nào để từ chối theo cú pháp async / await?


282

Làm cách nào tôi có thể từ chối lời hứa được trả về bởi hàm async / await?

vd

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

Dịch sang 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.
  });
}

Vì vậy, làm thế nào tôi có thể từ chối lời hứa này trong trường hợp này?


20
Tránh các Promiseantipotype xây dựng ! Ngay cả đoạn trích đầu tiên cũng nên được viếtfoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
Bergi

10
Tôi nghĩ sẽ rất hữu ích khi dịch mã trong câu hỏi này sang vanilla JS, vì câu hỏi không liên quan gì đến TypeScript. Nếu tôi làm như vậy thì chỉnh sửa đó có thể được chấp nhận?
Jacob Ford

Câu trả lời:


326

Tốt nhất là để throwmột Errorgói giá trị, mà kết quả trong một lời hứa từ chối với một Errorgói giá trị:

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

Bạn cũng có thể chỉ throwlà giá trị, nhưng sau đó không có thông tin theo dõi ngăn xếp:

} catch (error) {
    throw 400;
}

Thay phiên, trả lại một lời hứa bị từ chối với Errorgói giá trị, nhưng nó không phải là thành ngữ:

} catch (error) {
    return Promise.reject(new Error(400));
}

(Hoặc chỉ return Promise.reject(400);, nhưng một lần nữa, sau đó không có thông tin ngữ cảnh.)

(Trong trường hợp của bạn, vì bạn đang sử dụng TypeScriptfoogiá trị truy xuất là Promise<A>, bạn sẽ sử dụng return Promise.reject<A>(400 /*or error*/);)

Trong một tình huống async/ await, cái cuối cùng có lẽ là một chút của một trận đấu sai ngữ nghĩa, nhưng nó hoạt động.

Nếu bạn ném một cái Error, nó chơi tốt với mọi thứ tiêu tốn fookết quả của bạn bằng awaitcú pháp:

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

12
Và vì async / await là về việc đưa luồng async trở lại cú pháp đồng bộ hóa, throwtốt hơn Promise.reject()IMO. Cho dù throw 400là một câu hỏi khác nhau. Trong OP, nó đang từ chối 400 và chúng ta có thể lập luận rằng nó nên từ chối Errorthay thế.
unional

2
Có, tuy nhiên, nếu chuỗi mã của bạn thực sự sử dụng async / await, thì bạn sẽ ..... khó nhập vào đây, hãy để tôi giới thiệu dưới dạng câu trả lời
liên quan

1
Có bất kỳ lý do nào bạn muốn đưa ra một lỗi mới trái ngược với lỗi được đưa ra cho bạn trong khối bắt không?
Adrian M

1
@sebastian - Tôi không biết ý của bạn ở đó. Trong các asyncchức năng, không có resolvehoặc rejectchức năng. Có returnthrow, đó là những cách thành ngữ để giải quyết và từ chối asynclời hứa của chức năng.
TJ Crowder

1
@ Jan-PhilipGehrcke - Bạn có thể , nhưng tôi không bao giờ làm thế. Nó đang tạo ra một thể hiện, newlàm cho nó rõ ràng. Cũng lưu ý rằng bạn không thể loại bỏ nó nếu bạn có một Errorlớp con ( class MyError extends Error), vì vậy ...
TJ Crowder

146

Có lẽ cũng nên đề cập rằng bạn chỉ có thể xâu chuỗi một catch()chức năng sau khi cuộc gọi hoạt động không đồng bộ của bạn vì dưới mui xe vẫn có một lời hứa được trả lại.

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

Bằng cách này bạn có thể tránh try/catchcú pháp nếu bạn không thích nó.


1
Vì vậy, nếu tôi muốn từ chối asyncchức năng của mình , tôi ném ngoại lệ và sau đó bắt nó một cách độc đáo .catch()giống như khi tôi quay lại Promise.rejecthoặc gọi reject. Tôi thích nó!
icl7126

7
Tôi không hiểu tại sao đây phải là câu trả lời được chấp nhận. Không chỉ là câu trả lời được chấp nhận sạch hơn, mà nó còn xử lý tất cả các awaitlỗi có thể xảy ra trong một thói quen. Trừ khi các trường hợp rất cụ thể là cần thiết cho mỗi trường hợp awaittôi không thấy lý do tại sao bạn muốn bắt chúng như thế này. Chỉ cần tôi khiêm tốn ý kiến.
edgaralienfoe

1
@jablesauce cho trường hợp sử dụng của tôi, tôi không chỉ cần phải nắm bắt từng awaitthất bại một cách riêng biệt, mà tôi còn cần phải làm việc với một khung dựa trên Promise đã từ chối các lời hứa bị lỗi.
Reuven Karasik

Nó không làm việc cho tôi. Dường như không có trong khối bắt nếu url thất bại. [phản ứng] = chờ đợi oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (err => {logger.error ( 'Không thể lấy quyền kho', err); callback (err);})
sn.anurag

1
không cần awaittừ khóa ở đây.
Ashish Rawat

12

Bạn có thể tạo một hàm bao bọc nhận lời hứa và trả về một mảng có dữ liệu nếu không có lỗi và lỗi nếu có lỗi.

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

Sử dụng nó như thế này trong ES7 và trong một async chức năng:

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
}

1
Trông giống như một nỗ lực để có cú pháp Go đáng yêu nhưng không có nhiều sự thanh lịch. Tôi thấy mã sử dụng nó bị xáo trộn vừa đủ để hút giá trị ra khỏi giải pháp.
Kim

8

Cách tốt hơn để viết chức năng async sẽ là bằng cách trả lại một Promise đang chờ xử lý từ đầu và sau đó xử lý cả từ chối và giải quyết trong cuộc gọi lại của lời hứa, thay vì chỉ đưa ra một lời hứa bị từ chối do lỗi. Thí dụ:

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

Sau đó, bạn chỉ cần chuỗi phương thức trên lời hứa trả lại:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

Nguồn - hướng dẫn này:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise


5
Câu hỏi được hỏi cụ thể về việc sử dụng async / await. Không sử dụng lời hứa
Mak

Câu trả lời này không có nghĩa là câu trả lời chính xác. Đây là một câu trả lời hỗ trợ cho các câu trả lời khác được đưa ra ở trên. Tôi sẽ đặt nó xuống như một bình luận nhưng cho rằng tôi có mã, trường trả lời là một nơi tốt hơn.
OzzyTheGiant

Cảm ơn đã làm rõ. Chỉ ra cách tạo một hàm async chắc chắn rất hữu ích. Cập nhật khối mã thứ hai để sử dụng đang chờ sẽ có liên quan và hữu ích hơn rất nhiều. Chúc mừng
Mak

Tôi đã chỉnh sửa phản hồi của bạn để cập nhật nó. Hãy cho tôi biết nếu tôi bỏ lỡ điều gì đó
Mak

4

Tôi có một đề nghị để xử lý các từ chối đúng cách trong một cách tiếp cận mới lạ, mà không có nhiều khối thử bắt.

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

Nơi nhập hàm to.ts từ:

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

Tín dụng đi đến Dima Grossman trong liên kết sau .


1
Tôi sử dụng cấu trúc này gần như độc quyền (sạch hơn nhiều) và có một mô-đun 'to' đã xuất hiện trong một lúc npmjs.com/package/await-to-js . Đừng cần khai báo riêng biệt chỉ cần đặt trước mặt của bài tập được giải mã. Cũng có thể làm chỉ let [err]=khi kiểm tra lỗi.
DKebler

3

Đây không phải là câu trả lời cho câu hỏi của @TJ Crowder. Chỉ là một bình luận phản hồi bình luận "Và thực tế, nếu ngoại lệ sẽ được chuyển thành từ chối, tôi không chắc liệu tôi có thực sự bị làm phiền nếu đó là Lỗi hay không. Lý do của tôi chỉ ném Lỗi có thể không áp dụng. "

nếu mã của bạn đang sử dụng async/ await, thì đó vẫn là một cách tốt để từ chối Errorthay vì 400:

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

3

Tôi biết đây là một câu hỏi cũ, nhưng tôi chỉ vấp phải chủ đề và dường như có một sự nhầm lẫn ở đây giữa các lỗi và từ chối chạy afoul (ít nhất là trong nhiều trường hợp) về lời khuyên lặp đi lặp lại không sử dụng xử lý ngoại lệ để đối phó với các trường hợp dự đoán. Để minh họa: nếu phương thức async đang cố xác thực người dùng và xác thực không thành công, đó là từ chối (một trong hai trường hợp dự đoán) và không phải là lỗi (ví dụ: nếu API xác thực không khả dụng.)

Để chắc chắn rằng tôi không chỉ chẻ sợi tóc, tôi đã chạy thử nghiệm hiệu suất của ba cách tiếp cận khác nhau, sử dụng mã này:

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

Một số nội dung trong đó được đưa vào vì sự không chắc chắn của tôi liên quan đến trình thông dịch Javascript (tôi chỉ muốn đi xuống một lỗ thỏ tại một thời điểm); chẳng hạn, tôi đã bao gồm doSomethinghàm và gán trở lại của nó dummyValueđể đảm bảo rằng các khối có điều kiện sẽ không được tối ưu hóa.

Kết quả của tôi là:

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

Tôi biết rằng có rất nhiều trường hợp không đáng để tìm kiếm những tối ưu hóa nhỏ, nhưng trong các hệ thống quy mô lớn hơn, những điều này có thể tạo ra sự khác biệt tích lũy lớn, và đó là một so sánh khá rõ ràng.

Trong khi tôi nghĩ rằng cách tiếp cận của câu trả lời được chấp nhận là hợp lý trong trường hợp bạn muốn xử lý các lỗi không thể đoán trước trong một hàm không đồng bộ, trong trường hợp từ chối đơn giản có nghĩa là "bạn sẽ phải đi với Kế hoạch B (hoặc C, hoặc Dạn) "Tôi nghĩ rằng sở thích của tôi sẽ là từ chối sử dụng một đối tượng phản hồi tùy chỉnh.


2
Ngoài ra, hãy nhớ rằng bạn không cần phải căng thẳng về việc xử lý các lỗi không lường trước được trong một hàm async nếu lệnh gọi đến hàm đó nằm trong khối thử / bắt trong phạm vi kèm theo vì - không giống như Promise - các hàm async sẽ đưa lỗi của chúng vào kèm theo phạm vi, trong đó chúng được xử lý giống như lỗi cục bộ trong phạm vi đó. Đó là một trong những đặc quyền chính của async / await!
RiqueW

Microbenchmark là ma quỷ. Nhìn gần hơn vào các con số. Bạn cần phải làm một cái gì đó 1000x để nhận thấy sự khác biệt 1ms ở đây. Có, thêm ném / bắt sẽ mở rộng chức năng. Nhưng a) nếu bạn đang chờ đợi một cái gì đó không đồng bộ, có thể mất một số đơn đặt hàng cường độ sẽ mất hơn 0,0005 Ms để xảy ra trong nền. b) bạn cần thực hiện 1000 lần để tạo sự khác biệt 1ms ở đây.
Jamie Pate
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.