2017-03-28 30 views
11

Giả sử chúng tôi có 3 tác vụ không đồng bộ trả lại Lời hứa: A, BC. Chúng tôi muốn chuỗi chúng lại với nhau (có nghĩa là, vì lợi ích của sự rõ ràng, lấy giá trị trả về bởi A và gọi B với nó), nhưng cũng muốn xử lý các lỗi chính xác cho mỗi và thoát ra ở thất bại đầu tiên. Hiện nay, tôi thấy 2 cách để làm điều này:Làm thế nào để xử lý lỗi đúng trong chuỗi Promise?

A 
.then(passA) 
.then(B) 
.then(passB) 
.then(C) 
.then(passC) 
.catch(failAll) 

Ở đây, passX chức năng xử lý mỗi sự thành công của các cuộc gọi đến X. Nhưng trong hàm failAll, chúng tôi phải xử lý tất cả các lỗi của A, BC, điều này có thể phức tạp và không dễ đọc, đặc biệt nếu chúng tôi có nhiều hơn 3 tác vụ không đồng bộ. Vì vậy, theo cách khác mất này vào xem xét:

A 
.then(passA, failA) 
.then(B) 
.then(passB, failB) 
.then(C) 
.then(passC, failC) 
.catch(failAll) 

Ở đây, chúng tôi tách ra logic của bản gốc failAll vào failA, failBfailC, mà dường như đơn giản và dễ đọc, vì tất cả các lỗi được xử lý ngay bên cạnh nguồn của nó . Tuy nhiên, điều này không làm những gì tôi muốn.

Hãy xem nếu A không thành công (bị từ chối), failA không được tiếp tục gọi B, do đó phải ném ngoại lệ hoặc từ chối cuộc gọi. Nhưng cả hai trường hợp này đều bị bắt bởi failBfailC, nghĩa là failBfailC cần biết nếu chúng tôi đã thất bại hoặc không, có lẽ là giữ trạng thái (tức là biến).

Hơn nữa, có vẻ như chúng tôi có nhiều tác vụ không đồng bộ hơn, chức năng failAll của chúng tôi tăng kích thước (cách 1) hoặc nhiều hơn failX chức năng được gọi (cách 2). Điều này đưa tôi đến câu hỏi của tôi:

Có cách nào tốt hơn để thực hiện việc này không?

Xem xét: Vì ngoại lệ trong then được xử lý bằng phương pháp từ chối, nên có phương pháp Promise.throw để thực sự ngắt chuỗi?

A possible duplicate, với câu trả lời bổ sung thêm phạm vi bên trong trình xử lý. Không phải là hứa hẹn phải tôn vinh chuỗi tuyến tính các hàm, và không truyền các hàm truyền các hàm truyền các hàm?

+1

trùng lặp có thể xảy ra của [chuỗi lời hứa Break và gọi một chức năng dựa trên các bước trong chuỗi nơi nó bị hỏng (bị từ chối)] (http://stackoverflow.com/questions/20714460/break-promise-chain-and-call-a-function-based-on-the-step-in-the-chain-where-it) – wing

+0

@wing Xin lỗi, tôi cũng nên làm rõ điều này - trong hàm 'passX' và' failX', tôi thực sự "đang xử lý thành công" của cuộc gọi đến 'X'. Ví dụ, 'A' là một cuộc gọi GET XMLHTTPRequest đến một API, và' passA' cập nhật trạng thái của đối tượng DOM tương ứng để nói "được tìm nạp", trước khi tiếp tục gọi một API 'B' khác. Tôi sẽ ghi chú điều này trong câu hỏi của mình. –

+0

"* Không hứa hẹn sẽ tôn trọng chuỗi tuyến tính của các chức năng *" - không phải để xử lý lỗi có nhiều hơn chuỗi. Từ [Cách thích hợp để bỏ qua một chức năng sau đó trong lời hứa] (http://stackoverflow.com/q/21576862/1048572): * Đối với phân nhánh, bạn sẽ luôn cần một mức bổ sung làm tổ. * Điểm hứa hẹn chỉ là bạn vẫn có thể 'return' ra khỏi các hàm lồng nhau đó, thay vì cần gọi lại một cuộc gọi lại. – Bergi

Trả lời

5

Bạn có một vài tùy chọn. Trước tiên, hãy xem liệu tôi có thể chưng cất các yêu cầu của bạn không.

  1. Bạn muốn xử lý lỗi gần nơi xảy ra, do đó bạn không có một trình xử lý lỗi phải sắp xếp tất cả các lỗi khác nhau có thể để xem phải làm gì.

  2. Khi một lời hứa thất bại, bạn muốn có khả năng hủy bỏ phần còn lại của chuỗi.

Một khả năng là như thế này:

A().then(passA).catch(failA).then(val => { 
    return B(val).then(passB).catch(failB); 
}).then(val => { 
    return C(val).then(passC).catch(failC); 
}).then(finalVal => { 
    // chain done successfully here 
}).catch(err => { 
    // some error aborted the chain, may or may not need handling here 
    // as error may have already been handled by earlier catch 
}); 

Sau đó, trong mỗi failA, failB, failC, bạn nhận được lỗi cụ thể cho bước đó. Nếu bạn muốn hủy bỏ chuỗi, bạn sẽ trở lại trước khi hàm trả về. Nếu bạn muốn chuỗi tiếp tục, bạn chỉ cần trả về một giá trị bình thường.


Đoạn mã trên cũng có thể được viết như thế này (với hành vi hơi khác nếu passB hoặc passC ném hoặc trả về một lời hứa từ chối.

A().then(passA, failA).then(val => { 
    return B(val).then(passB, failB); 
}).then(val => { 
    return C(val).then(passC, failC); 
}).then(finalVal => { 
    // chain done successfully here 
}).catch(err => { 
    // some error aborted the chain, may or may not need handling here 
    // as error may have already been handled by earlier catch 
}); 

Bởi vì chúng nằm hoàn toàn lặp đi lặp lại, bạn có thể làm cho toàn bộ vật thể được điều khiển theo bảng cho bất kỳ độ dài chuỗi nào.

function runSequence(data) { 
    return data.reduce((p, item) => { 
     return p.then(item[0]).then(item[1]).catch(item[2]); 
    }, Promise.resolve()); 
} 

let fns = [ 
    [A, passA, failA], 
    [B, passB, failB], 
    [C, passC, failC] 
]; 

runSequence(fns).then(finalVal => { 
    // whole sequence finished 
}).catch(err => { 
    // sequence aborted with an error 
}); 

Một điểm hữu ích khác khi chuỗi nhiều lời hứa là nếu bạn tạo một lớp Lỗi duy nhất cho mỗi lỗi từ chối, bạn có thể dễ dàng chuyển đổi loại lỗi bằng cách sử dụng instanceof trong trình xử lý cuối cùng .catch() nếu bạn cần biết bước gây ra chuỗi bị hủy bỏ. Các thư viện như Bluebird, cung cấp các ngữ nghĩa cụ thể .catch() để tạo .catch() chỉ bắt được một loại lỗi cụ thể (giống như cách thử/bắt nó). Bạn có thể xem Bluebird làm như thế nào ở đây: http://bluebirdjs.com/docs/api/catch.html. Nếu bạn định xử lý từng lỗi ngay khi từ chối lời hứa của chính nó (như trong các ví dụ trên), thì điều này là không cần thiết trừ khi bạn vẫn cần biết ở bước cuối cùng .catch() bước nào gây ra lỗi.

3

Có hai cách mà tôi khuyên bạn (tùy thuộc vào những gì bạn đang cố gắng để hoàn thành với điều này):

Vâng, bạn muốn xử lý tất cả các lỗi trong chuỗi lời hứa với một nắm bắt duy nhất.

Nếu bạn cần phải biết cái nào thất bại, bạn có thể từ chối lời hứa với một thông điệp duy nhất hoặc giá trị như thế này:

A 
.then(a => { 
    if(!pass) return Promise.reject('A failed'); 
    ... 
}) 
.then(b => { 
    if(!pass) return Promise.reject('B failed'); 
    ... 
}) 
.catch(err => { 
    // handle the error 
}); 

Ngoài ra, bạn có thể trở lại những lời hứa khác bên trong .then

A 
.then(a => { 
    return B; // B is a different promise 
}) 
.then(b => { 
    return C; // C is another promise 
}) 
.then(c => { 
    // all promises were resolved 
    console.log("Success!") 
}) 
.catch(err => { 
    // handle the error 
    handleError(err) 
}); 

Trong mỗi lời hứa đó, bạn sẽ muốn một số loại thông báo lỗi duy nhất để bạn biết cái nào bị lỗi.

Và vì đây là các chức năng mũi tên, chúng tôi có thể loại bỏ các dấu ngoặc nhọn! Một lý do khác mà tôi yêu hứa hẹn là

A 
.then(a => B) 
.then(b => C) 
.then(c => console.log("Success!")) 
.catch(err => handleError(err)); 
0

Bạn có thể branch the promise-chain, nhưng thành thật mà nói, xử lý lỗi sớm không phải là cách thích hợp để làm điều đó, đặc biệt là vì lý do len như khả năng đọc. Đúng cho mã đồng bộ, tức làkhông try/catch mọi chức năng đơn lẻ hoặc khả năng đọc chỉ nằm trong thùng rác.

Luôn luôn vượt qua lỗi và "xử lý" chúng tại điểm nơi dòng mã tích cực tiếp tục.

Nếu bạn cần phải biết cách điều xa có, một thủ thuật tôi sử dụng là một tiến trình đơn giản truy cập:

let progress = ""; 
A() 
.then(a => (progress = "A passed", passA(a))) 
.then(B) 
.then(b => (progress = "B passed", passB(b))) 
.then(C) 
.then(c => (progress = "C passed", passC(c))) 
.catch(err => (console.log(progress), failAll(err))) 
+0

Nhưng sau đó 'failB' sẽ được gọi với' null' khi 'A' thất bại. Tôi nghĩ đó là chính xác những gì anh ta không muốn. – Bergi

+0

@Bergi bạn nói đúng. Tôi đã thay đổi câu trả lời của mình. Cảm ơn! – jib

Các vấn đề liên quan