Lời hứa xây dựng lời hứa rõ ràng là gì và làm thế nào để tôi tránh nó?


516

Tôi đã viết mã làm một cái gì đó trông giống như:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Có người nói với tôi cái này được gọi là " antipotype " hoặc " Promiseantipotype " tương ứng, cái gì xấu về mã này và tại sao cái này được gọi là antipotype ?


Tôi có thể xác nhận rằng việc loại bỏ điều này là (trong ngữ cảnh bên phải, không phải bên trái), loại bỏ getStuffDonetrình bao bọc hàm và chỉ sử dụng chữ Promise?
Dembinski

1
hoặc có catchkhối trong lớp getStuffDonebọc antipotype?
Dembinski

1
Ít nhất là đối với Promiseví dụ bản địa, bạn cũng có các hàm bao hàm không cần thiết cho trình xử lý .then.catchtrình xử lý (nghĩa là có thể như vậy .then(resolve).catch(reject).) Một cơn bão hoàn hảo của các mẫu chống.

6
@NoahFreitas mã đó được viết theo cách đó cho mục đích mô phạm. Tôi đã viết câu hỏi và câu trả lời này để giúp những người gặp phải vấn đề này sau khi đọc rất nhiều mã giống như vậy :)
Benjamin Gruenbaum

Xem thêm stackoverflow.com/questions/57661537/ để biết cách loại bỏ không chỉ xây dựng Promise rõ ràng mà còn sử dụng biến toàn cục.
David Spector

Câu trả lời:


357

Các antipattern hoãn lại (bây giờ rõ ràng-xây dựng antipattern) đặt ra bởi Esailija là một dân tộc antipattern chung người chưa quen với những lời hứa thực hiện, tôi đã thực hiện nó bản thân mình khi tôi lần đầu tiên sử dụng những lời hứa. Vấn đề với mã trên là thất bại trong việc sử dụng chuỗi hứa hẹn.

Lời hứa có thể xâu chuỗi .thenvà bạn có thể trả lại lời hứa trực tiếp. Mã của bạn getStuffDonecó thể được viết lại thành:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

Hứa hẹn là tất cả về việc làm cho mã không đồng bộ dễ đọc hơn và hoạt động giống như mã đồng bộ mà không che giấu thực tế đó. Promise đại diện cho một sự trừu tượng hóa trên một giá trị của hoạt động một lần, chúng trừu tượng hóa khái niệm của một tuyên bố hoặc biểu thức trong ngôn ngữ lập trình.

Bạn chỉ nên sử dụng các đối tượng bị trì hoãn khi bạn đang chuyển đổi API thành các lời hứa và không thể tự động thực hiện hoặc khi bạn viết các hàm tổng hợp được thể hiện dễ dàng hơn theo cách này.

Trích dẫn Esailija:

Đây là mô hình chống phổ biến nhất. Thật dễ dàng rơi vào điều này khi bạn không thực sự hiểu lời hứa và nghĩ về chúng như các trình phát sự kiện được tôn vinh hoặc tiện ích gọi lại. Chúng ta hãy tóm tắt lại: các lời hứa là về việc tạo mã không đồng bộ giữ lại hầu hết các thuộc tính bị mất của mã đồng bộ như thụt lề phẳng và một kênh ngoại lệ.


@BenjaminGruenbaum: Tôi tự tin vào việc sử dụng hoãn lại cho việc này, vì vậy không cần câu hỏi mới. Tôi chỉ nghĩ rằng đó là một trường hợp sử dụng mà bạn đã bỏ lỡ trong câu trả lời của bạn. Những gì tôi đang làm có vẻ giống với sự đối lập của tập hợp, phải không?
mrcvens

1
@mhelvens Nếu bạn chia thủ công API không gọi lại thành API hứa hẹn phù hợp với phần "chuyển đổi API gọi lại thành lời hứa". Antipotype nói về việc gói lời hứa trong một lời hứa khác mà không có lý do chính đáng, bạn không thực hiện lời hứa để bắt đầu nên không áp dụng ở đây.
Benjamin Gruenbaum

@BenjaminGruenbaum: Ah, mặc dù bản thân trì hoãn được coi là một mô hình chống đối, nhưng với bluebird thì không tán thành chúng, và bạn đề cập đến việc "chuyển đổi API thành lời hứa" (cũng là trường hợp không thực hiện lời hứa bắt đầu).
mrcvens

@mhelvens Tôi đoán dư thừa hoãn lại chống mẫu sẽ chính xác hơn cho những gì nó thực hiện. Bluebird không tán thành .defer()api vào công cụ xây dựng lời hứa mới hơn (và an toàn), nó không (không có cách nào) không tán thành khái niệm xây dựng lời hứa :)
Benjamin Gruenbaum

1
Cảm ơn bạn @ Roamer-1888 tài liệu tham khảo của bạn đã giúp tôi cuối cùng tìm ra vấn đề của tôi là gì. Có vẻ như tôi đang tạo ra những lời hứa lồng nhau (chưa được hoàn thành) mà không nhận ra điều đó.
ghuroo

134

Có gì sai với nó?

Nhưng mô hình hoạt động!

Bạn thật may mắn. Thật không may, nó có thể không, vì bạn có thể quên một số trường hợp cạnh. Trong hơn một nửa số lần tôi đã thấy, tác giả đã quên chăm sóc bộ xử lý lỗi:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

Nếu lời hứa khác bị từ chối, điều này sẽ không được chú ý thay vì được truyền đến lời hứa mới (nơi nó sẽ được xử lý) - và lời hứa mới sẽ tồn tại mãi trong thời gian chờ xử lý, có thể gây rò rỉ.

Điều tương tự cũng xảy ra trong trường hợp mã gọi lại của bạn gây ra lỗi - ví dụ: khi resultkhông có propertyngoại lệ và một ngoại lệ được đưa ra. Điều đó sẽ được giải quyết và để lại lời hứa mới chưa được giải quyết.

Ngược lại, việc sử dụng .then()sẽ tự động xử lý cả hai tình huống này và từ chối lời hứa mới khi xảy ra lỗi:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

Các antipotype bị trì hoãn không chỉ cồng kềnh, mà còn dễ bị lỗi . Sử dụng .then()để xích là an toàn hơn nhiều.

Nhưng tôi đã xử lý mọi thứ!

Có thật không? Tốt Tuy nhiên, điều này sẽ khá chi tiết và phong phú, đặc biệt nếu bạn sử dụng thư viện lời hứa hỗ trợ các tính năng khác như hủy bỏ hoặc chuyển tin nhắn. Hoặc có thể nó sẽ trong tương lai, hoặc bạn muốn trao đổi thư viện của bạn với một cái tốt hơn? Bạn sẽ không muốn viết lại mã của bạn cho điều đó.

Các phương thức của thư viện ( then) không chỉ hỗ trợ tất cả các tính năng, mà còn có thể có những tối ưu nhất định. Sử dụng chúng có thể sẽ làm cho mã của bạn nhanh hơn hoặc ít nhất là cho phép được tối ưu hóa bởi các phiên bản tương lai của thư viện.

Làm thế nào để tôi tránh nó?

Vì vậy, bất cứ khi nào bạn thấy mình tự tạo một Promisehoặc hoặc Deferrednhững lời hứa đã có sẵn, hãy kiểm tra API thư viện trước . Các antipotype bị trì hoãn thường được áp dụng bởi những người xem lời hứa [chỉ] như một mẫu quan sát - nhưng lời hứa còn hơn cả các cuộc gọi lại : chúng được cho là có thể ghép lại được. Mỗi thư viện phong nha đều có rất nhiều chức năng dễ sử dụng để sắp xếp các lời hứa theo mọi cách có thể nghĩ được, chăm sóc tất cả những thứ cấp thấp mà bạn không muốn đối phó.

Nếu bạn thấy cần phải soạn một số lời hứa theo cách mới không được hỗ trợ bởi chức năng trợ giúp hiện có, viết chức năng của riêng bạn với Trì hoãn không thể tránh khỏi sẽ là lựa chọn cuối cùng của bạn. Cân nhắc chuyển sang thư viện có nhiều tính năng hơn và / hoặc báo lỗi đối với thư viện hiện tại của bạn. Người bảo trì của nó sẽ có thể lấy được thành phần từ các chức năng hiện có, triển khai chức năng trợ giúp mới cho bạn và / hoặc giúp xác định các trường hợp cạnh cần xử lý.


Có các ví dụ, ngoài một hàm bao gồm setTimeout, trong đó hàm tạo có thể được sử dụng nhưng không được coi là "Promit constructor anitpotype"?
khách271314

1
@ guest271314: Mọi thứ không đồng bộ mà không trả lại lời hứa. Mặc dù thường xuyên, bạn có được kết quả tốt hơn với những người trợ giúp quảng bá chuyên dụng của thư viện. Và đảm bảo luôn luôn quảng bá ở mức thấp nhất, vì vậy đó không phải là " một chức năng bao gồmsetTimeout ", mà là " chính chức năng setTimeoutđó ".
Bergi

"Và đảm bảo luôn luôn quảng bá ở mức thấp nhất, vì vậy đó không phải là" một chức năng bao gồm setTimeout", mà là" setTimeoutchính chức năng "" Có thể mô tả, liên kết đến sự khác biệt, giữa hai?
guest271314

@ guest271314 Một chức năng chỉ bao gồm một cuộc gọi đến setTimeoutrõ ràng khác với chức năng setTimeoutđó , phải không?
Bergi

4
Tôi nghĩ rằng một trong những bài học quan trọng ở đây, một điều chưa được nêu rõ cho đến nay, đó là một Promise và chuỗi 'rồi' của nó đại diện cho một hoạt động không đồng bộ: hoạt động ban đầu nằm trong hàm tạo Promise và điểm cuối cuối cùng nằm trong ' rồi 'chức năng. Vì vậy, nếu bạn có một hoạt động đồng bộ hóa theo sau là một hoạt động không đồng bộ, hãy đặt công cụ đồng bộ hóa vào Lời hứa. Nếu bạn có thao tác không đồng bộ theo sau là đồng bộ hóa, hãy đặt nội dung đồng bộ hóa vào 'sau đó'. Trong trường hợp đầu tiên, trả lại Lời hứa ban đầu. Trong trường hợp thứ hai, trả về chuỗi Promise / then (cũng là một Promise).
David Spector
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.