Antipattern สัญญาก่อสร้างที่ชัดเจนคืออะไรและฉันจะหลีกเลี่ยงได้อย่างไร


516

ฉันกำลังเขียนโค้ดที่ทำสิ่งที่ดูเหมือน:

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() */ |     });
}                                        | }

มีคนบอกฉันนี้จะเรียกว่า " antipattern รอการตัดบัญชี " หรือ " Promiseคอนสตรัค antipattern " ตามลำดับสิ่งที่ไม่ดีเกี่ยวกับรหัสนี้และทำไมนี้เรียกantipattern ?


ฉันสามารถยืนยันได้ว่าสิ่งที่จะนำสิ่งนี้ออกไปคือ (ในบริบทของด้านขวาไม่ใช่ซ้ายตัวอย่าง) ลบตัวgetStuffDoneหุ้มฟังก์ชั่นและเพียงแค่ใช้ตัวอักษรสัญญา
Dembinski

1
หรือมีcatchบล็อกในgetStuffDoneเสื้อคลุมที่ antipattern?
Dembinski

1
อย่างน้อยสำหรับPromiseตัวอย่างพื้นเมืองคุณยังมีฟังก์ชั่นห่อที่ไม่จำเป็นสำหรับ.thenและ.catchตัวจัดการ (นั่นอาจเป็นเพียง.then(resolve).catch(reject)) พายุที่สมบูรณ์แบบของรูปแบบการต่อต้าน
Noah Freitas

6
@NoahFreitas โค้ดนั้นเขียนขึ้นเพื่อวัตถุประสงค์ในการสอน ผมเขียนคำถามนี้และคำตอบในการที่จะช่วยให้ผู้คนที่วิ่งเข้ามาในเรื่องนี้หลังจากที่ได้อ่านมากรหัสมองเช่น :) ว่า
เบนจามิน Gruenbaum

ดูstackoverflow.com/questions/57661537/…สำหรับวิธีการกำจัดไม่เพียง แต่การสร้างสัญญาที่ชัดเจน แต่ยังใช้ตัวแปรทั่วโลกเช่นกัน
David Spector

คำตอบ:


357

antipattern ที่รอการตัดบัญชี (ตอนนี้ต่อต้านการสร้างรูปแบบที่ชัดเจน)ประกาศเกียรติคุณจากEsailijaเป็นคนต่อต้านรูปแบบทั่วไปที่ยังใหม่กับสัญญาที่ทำฉันได้ทำมันเองเมื่อฉันใช้สัญญาครั้งแรก ปัญหาเกี่ยวกับรหัสข้างต้นคือการที่ล้มเหลวในการใช้ความจริงที่สัญญาโซ่

สัญญาสามารถเชื่อมโยงกับ.thenและคุณสามารถคืนสัญญาได้โดยตรง รหัสของคุณในgetStuffDoneสามารถเขียนใหม่เป็น:

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

คำสัญญาทั้งหมดเกี่ยวกับการทำให้โค้ดอะซิงโครนัสอ่านได้ง่ายขึ้นและทำงานเหมือนกับโค้ดซิงโครนัสโดยไม่ปิดบังข้อเท็จจริงนั้น คำสัญญาหมายถึงสิ่งที่เป็นนามธรรมเกี่ยวกับคุณค่าของการดำเนินการในครั้งเดียวซึ่งเป็นนามธรรมความคิดของคำสั่งหรือการแสดงออกในภาษาการเขียนโปรแกรม

คุณควรใช้วัตถุที่เลื่อนออกไปเมื่อคุณแปลง API เป็นสัญญาและไม่สามารถทำได้โดยอัตโนมัติหรือเมื่อคุณกำลังเขียนฟังก์ชั่นการรวมที่ง่ายขึ้นด้วยวิธีนี้

การอ้างอิงภาษา:

นี่คือรูปแบบการต่อต้านที่พบมากที่สุด มันง่ายที่จะตกอยู่ในนี้เมื่อคุณไม่เข้าใจสัญญาและคิดว่าพวกเขาเป็นเหตุการณ์ที่เปล่งประกายหรือยูทิลิตี้โทรกลับ สรุปกันเถอะ: สัญญาเกี่ยวกับการสร้างรหัสอะซิงโครนัสเพื่อรักษาคุณสมบัติส่วนใหญ่ที่หายไปของรหัสซิงโครนัสเช่นการเยื้องแบบแบนและช่องข้อยกเว้นหนึ่งช่อง


@BenjaminGruenbaum: ฉันมีความมั่นใจในการใช้งานของการรอการตัดบัญชีนี้ดังนั้นไม่จำเป็นต้องมีคำถามใหม่ ฉันแค่คิดว่ามันเป็นกรณีใช้งานที่คุณขาดหายไปจากคำตอบของคุณ สิ่งที่ฉันทำดูเหมือนกับการรวมตัวกันใช่มั้ย
mhelvens

1
@mhelvens หากคุณแบ่ง API ที่ไม่ใช่โทรกลับด้วยตนเองเป็น API สัญญาที่เหมาะกับส่วน "แปลง callback API เป็นสัญญา" Antipattern นั้นเกี่ยวกับการห่อสัญญาไว้ในอีกสัญญาหนึ่งโดยไม่มีเหตุผลที่ดีคุณไม่ได้ห่อหุ้มสัญญาที่จะเริ่มต้นด้วยดังนั้นจึงไม่มีผลบังคับใช้
Benjamin Gruenbaum

@BenjaminGruenbaum: อ่าฉันคิดว่าตัวเองถือว่าเป็นรูปแบบการต่อต้านสิ่งที่มี Bluebird แย้งพวกเขาและคุณพูดถึง "การแปลง API เป็นสัญญา" (ซึ่งก็เป็นกรณีที่ไม่ได้สัญญาว่าจะเริ่มต้นด้วย)
mhelvens

@ ช่วยฉันเดาว่ารูปแบบการต่อต้านที่รอการตัดบัญชีส่วนเกินจะแม่นยำยิ่งขึ้นสำหรับสิ่งที่มันทำ ครามเลิกใช้.defer()API เข้าใหม่ (และโยนความปลอดภัย) คอนสตรัคสัญญามันไม่ได้ (ในทางไม่) เลิกความคิดของสัญญาก่อสร้าง :)
เบนจามิน Gruenbaum

1
ขอบคุณ @ Roamer-1888 การอ้างอิงของคุณได้ช่วยฉันในการหาว่าปัญหาของฉันคืออะไร ดูเหมือนว่าฉันจะสร้างสัญญาที่ซ้อนกัน (ไม่ได้ส่งคืน) โดยไม่ทราบว่าเป็นเช่นนั้น
ghuroo

134

มีอะไรผิดปกติกับมัน?

แต่รูปแบบการทำงาน!

โชคดีนะคุณ. น่าเสียดายที่มันอาจไม่ได้เพราะคุณอาจลืมกรณีขอบบางอย่าง ในสิ่งที่เกิดขึ้นกว่าครึ่งที่ฉันได้เห็นผู้เขียนลืมที่จะจัดการกับตัวจัดการข้อผิดพลาด:

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

หากสัญญาอื่นถูกปฏิเสธสิ่งนี้จะเกิดขึ้นโดยไม่มีใครสังเกตเห็นแทนที่จะเผยแพร่ไปยังสัญญาใหม่ (ซึ่งจะได้รับการจัดการ) - และสัญญาใหม่จะคงอยู่ตลอดไปซึ่งจะทำให้เกิดการรั่วไหล

สิ่งเดียวกันนี้เกิดขึ้นในกรณีที่รหัสโทรกลับของคุณทำให้เกิดข้อผิดพลาด - เช่นเมื่อresultไม่มีpropertyและข้อยกเว้นถูกโยนทิ้ง นั่นจะไปไม่ได้จัดการและปล่อยให้สัญญาใหม่ไม่ได้รับการแก้ไข

ในทางตรงกันข้ามการใช้.then()จะดูแลทั้งสองสถานการณ์เหล่านี้โดยอัตโนมัติและปฏิเสธสัญญาใหม่เมื่อเกิดข้อผิดพลาด:

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

Antipattern ที่เลื่อนออกไปไม่เพียง แต่ยุ่งยาก แต่ยังมีข้อผิดพลาดได้ง่ายอีกด้วย ใช้.then()สำหรับผูกมัดปลอดภัยมากขึ้น

แต่ฉันจัดการทุกอย่างแล้ว!

จริงๆ? ดี. อย่างไรก็ตามจะมีรายละเอียดและค่อนข้างมากโดยเฉพาะหากคุณใช้ไลบรารี่สัญญาที่รองรับคุณสมบัติอื่น ๆ เช่นการยกเลิกหรือการส่งข้อความ หรืออาจจะเป็นในอนาคตหรือคุณต้องการเปลี่ยนห้องสมุดของคุณกับห้องสมุดที่ดีกว่า คุณจะไม่ต้องการเขียนรหัสของคุณอีกครั้ง

วิธีการของไลบรารี ( then) ไม่เพียง แต่รองรับคุณสมบัติทั้งหมดเท่านั้น แต่ยังอาจมีการเพิ่มประสิทธิภาพบางอย่าง การใช้พวกมันจะทำให้โค้ดของคุณเร็วขึ้นหรืออย่างน้อยก็ยอมให้มีการปรับให้เหมาะสมโดยการแก้ไขในอนาคตของไลบรารี

ฉันจะหลีกเลี่ยงได้อย่างไร

ดังนั้นเมื่อใดก็ตามที่คุณพบว่าตัวเองด้วยตนเองสร้างPromiseหรือDeferredแล้วและสัญญาที่มีอยู่มีความเกี่ยวข้องกับการตรวจสอบ API ห้องสมุดแรก antipattern รอตัดบัญชีมักจะถูกนำไปใช้โดยคนที่เห็นสัญญา [เท่านั้น] เป็นรูปแบบการสังเกตการณ์ - แต่สัญญาที่มีมากขึ้นกว่าการเรียกกลับพวกเขาควรจะเป็น composable ห้องสมุดที่ดีทุกแห่งมีฟังก์ชั่นที่ใช้งานง่ายมากมายสำหรับการจัดองค์ประกอบของคำสัญญาในทุก ๆ วิธีที่คิดได้ดูแลทุกสิ่งในระดับต่ำที่คุณไม่ต้องการจัดการ

หากคุณพบว่าจำเป็นต้องเขียนคำสัญญาในวิธีใหม่ที่ไม่ได้รับการสนับสนุนโดยฟังก์ชันตัวช่วยที่มีอยู่การเขียนฟังก์ชันของคุณเองด้วย Deferreds Deferreds ควรเป็นตัวเลือกสุดท้ายของคุณ พิจารณาเปลี่ยนเป็นไลบรารี่ที่มีฟีเจอร์มากกว่าและ / หรือไฟล์บั๊กกับไลบรารี่ปัจจุบันของคุณ ผู้ดูแลควรจะได้รับองค์ประกอบจากฟังก์ชั่นที่มีอยู่ใช้ฟังก์ชั่นผู้ช่วยใหม่สำหรับคุณและ / หรือช่วยในการระบุกรณีขอบที่ต้องได้รับการจัดการ


มีตัวอย่างนอกเหนือจากฟังก์ชั่นรวมถึงsetTimeoutที่ซึ่งคอนสตรัคเตอร์สามารถใช้งานได้ แต่ไม่ได้รับการพิจารณาว่าเป็น "Promise constructor anitpattern" หรือไม่?
guest271314

1
@ guest271314: ทุกอย่างแบบอะซิงโครนัสที่ไม่ส่งคืนสัญญา แม้ว่าบ่อยครั้งมากพอที่คุณจะได้รับผลลัพธ์ที่ดีขึ้นด้วยผู้ช่วยการแนะนำเฉพาะของห้องสมุด และตรวจสอบให้แน่ใจว่าได้ให้สัญญาในระดับต่ำสุดเสมอดังนั้นจึงไม่ใช่ " ฟังก์ชั่นรวมถึงsetTimeout " แต่ " ฟังก์ชั่นของsetTimeoutตัวเอง "
Bergi

"และตรวจสอบให้แน่ใจว่าจะให้สัญญาที่ระดับต่ำสุดเสมอดังนั้นจึงไม่ใช่" ฟังก์ชั่นรวมถึงsetTimeout"แต่" ฟังก์ชั่นของsetTimeoutตัวเอง ""สามารถอธิบายลิงก์ไปสู่ความแตกต่างระหว่างทั้งสองได้หรือไม่?
guest271314

@ guest271314 ฟังก์ชั่นที่เพิ่งมีการเรียกไปยังsetTimeoutเห็นได้ชัดว่าแตกต่างจากฟังก์ชั่นsetTimeoutของตัวเองไม่ได้?
Bergi

4
ฉันคิดว่าหนึ่งในบทเรียนที่สำคัญที่นี่ซึ่งยังไม่ได้ระบุไว้อย่างชัดเจนคือสัญญาและการถูกล่ามโซ่ 'แล้ว' แสดงถึงการดำเนินการแบบอะซิงโครนัสเดียว: การดำเนินการเริ่มต้นอยู่ในตัวสร้างสัญญาและท้ายที่สุดอยู่ใน จากนั้นฟังก์ชั่น ดังนั้นหากคุณมีการดำเนินการซิงค์ตามด้วยการดำเนินการแบบ Asynch ให้ใส่ข้อมูลการซิงค์ลงในสัญญา หากคุณมีการดำเนินการ async ตามด้วยการซิงค์ใส่สิ่งที่ซิงค์ใน 'แล้ว' ในกรณีแรกคืนสัญญาเดิม ในกรณีที่สองส่งคืนสัญญา / เชน (ซึ่งก็คือสัญญาด้วย)
David Spector
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.