บทความนี้ส่วนใหญ่เป็นที่นิยมในการใช้สัญญา
เป็นเวลานาน JavaScript ได้จัดการแบบอะซิงโครนัสในรูปแบบของการโทรกลับและกลไกการโทรกลับในด้านการพัฒนาส่วนหน้านั้นเกือบจะหยั่งรากลึกในใจของผู้คน เมื่อออกแบบ APIs ไม่ว่าจะเป็นผู้ผลิตเบราว์เซอร์นักพัฒนา SDK หรือผู้เขียนห้องสมุดต่างๆ
ในช่วงไม่กี่ปีที่ผ่านมาด้วยวุฒิภาวะที่ค่อยเป็นค่อยไปของรูปแบบการพัฒนาจาวาสคริปต์ข้อกำหนดของ CommonJS ได้เกิดมารวมถึงข้อเสนอของข้อกำหนดของสัญญา คำสัญญาได้เปลี่ยนการเขียนโปรแกรม JS แบบอะซิงโครนัสอย่างสมบูรณ์ทำให้การเขียนโปรแกรมแบบอะซิงโครนัสเข้าใจง่ายมาก
ในรูปแบบการโทรกลับเราคิดว่าต้องดำเนินการคิวแบบอะซิงโครนัสและรหัสอาจมีลักษณะเช่นนี้:
loadimg ('a.jpg', function () {loadimg ('b.jpg', function () {loadimg ('c.jpg', ฟังก์ชั่น () {console.log ('เสร็จสิ้นทั้งหมด!');});});});นี่คือสิ่งที่เรามักจะโทรกลับพีระมิด เมื่อมีงานอะซิงโครนัสจำนวนมากการรักษาการโทรกลับจำนวนมากจะเป็นหายนะ ทุกวันนี้ Node.js เป็นที่นิยมมาก ดูเหมือนว่าหลายทีมต้องการใช้มันเพื่อทำบางสิ่งบางอย่างเพื่อให้เข้ากับ "แฟชั่น" เมื่อพูดคุยกับเพื่อนร่วมชั้นการผ่าตัดและการบำรุงรักษาพวกเขายังวางแผนที่จะใช้ Node.js เพื่อทำอะไรบางอย่าง แต่เมื่อพวกเขานึกถึงเลเยอร์ของการโทรกลับของ JS พวกเขาจะท้อแท้
ตกลงเรื่องไร้สาระสิ้นสุดลงมาที่หัวข้อ
สัญญาอาจคุ้นเคยกับทุกคนเพราะข้อกำหนดของสัญญาได้ออกมาเป็นเวลานานและสัญญาได้ถูกรวมไว้ใน ES6 และเบราว์เซอร์ Chrome และ Firefox รุ่นที่สูงขึ้นได้ดำเนินการตามสัญญา แต่มี API น้อยกว่าห้องสมุดชั้นเรียนสัญญายอดนิยมในปัจจุบัน
สัญญาที่เรียกว่าสามารถเข้าใจได้อย่างแท้จริงว่าเป็น "สัญญา" ซึ่งหมายความว่าการโทร B, B ส่งคืน "สัญญา" ไปยัง A แล้ว A สามารถเขียนสิ่งนี้ได้เมื่อเขียนแผน: เมื่อ B ส่งคืนผลลัพธ์ให้ฉัน ในทางตรงกันข้ามหาก B ไม่ได้ให้ผลลัพธ์ที่ต้องการด้วยเหตุผลบางอย่างให้ดำเนินการตามแผนฉุกเฉิน S2 เพื่อให้ความเสี่ยงที่อาจเกิดขึ้นทั้งหมดอยู่ในช่วงควบคุมของ A
ประโยคข้างต้นแปลเป็นรหัสคล้ายกับ:
var resb = b (); var runa = function () {resb.then (execs1, execs2);}; runa ();เพียงแค่ดูที่บรรทัดของรหัสข้างต้นดูเหมือนว่าไม่มีอะไรพิเศษ แต่ความจริงอาจซับซ้อนกว่านี้มาก เพื่อให้บรรลุสิ่งหนึ่งอาจขึ้นอยู่กับการตอบสนองของมากกว่าหนึ่งคน B. อาจต้องถามคนหลายคนในเวลาเดียวกันจากนั้นดำเนินการตามแผนต่อไปหลังจากได้รับคำตอบทั้งหมด การแปลสุดท้ายเป็นรหัสอาจมีลักษณะเช่นนี้:
var resb = b (); var resc = c (); ... var runa = function () {reqb .then (resc, execs2). จากนั้น (resd, execs3). จากนั้น (rese, execs4) ...ที่นี่มีการใช้กลไกการประมวลผลที่แตกต่างกันเมื่อแต่ละคำถามตอบสนองที่ไม่สอดคล้องกับความคาดหวัง ในความเป็นจริงข้อมูลจำเพาะสัญญาไม่ต้องการสิ่งนี้และคุณไม่สามารถทำอะไรได้เลย (เช่นไม่ผ่านพารามิเตอร์ที่สองของตอนนั้น) หรือจัดการอย่างสม่ำเสมอ
โอเคมาทำความรู้จักกับข้อกำหนด/A+:
then (อาจกล่าวได้ว่าเป็นแกนหลักของสัญญา) จากนั้นจะต้องส่งคืนสัญญา จากนั้นสัญญาเดียวกันสามารถเรียกได้หลายครั้งและลำดับการดำเนินการของการเรียกกลับนั้นสอดคล้องกับคำสั่งซื้อเมื่อกำหนดไว้อย่างที่คุณเห็นมีเนื้อหาไม่มากในข้อกำหนดของสัญญาดังนั้นคุณสามารถลองใช้สัญญาต่อไปนี้ด้วยตัวคุณเอง
ต่อไปนี้เป็นการดำเนินการตามคำสัญญาอย่างง่าย ๆ ที่ฉันได้อ้างอิงห้องสมุดสัญญามากมาย โปรดย้ายไปที่ Promisea ในรหัส
การวิเคราะห์สั้น ๆ ของแนวคิด:
ตัวสร้างสัญญายอมรับ resolver ฟังก์ชั่นซึ่งสามารถเข้าใจได้ว่าเป็นการผ่านงานอะซิงโครนัส ตัวแก้ไขยอมรับพารามิเตอร์สองตัวหนึ่งคือการโทรกลับเมื่อประสบความสำเร็จและอีกอันคือการโทรกลับเมื่อล้มเหลว พารามิเตอร์ทั้งสองนี้เท่ากับพารามิเตอร์ที่ผ่านมา
ประการที่สองคือการดำเนินการในตอนนั้น เนื่องจากสัญญานั้นกำหนดให้ต้องส่งคืนสัญญาสัญญาใหม่จะถูกสร้างขึ้นเมื่อเรียกว่าซึ่งจะถูกแขวนไว้บน _next ของสัญญาปัจจุบัน การโทรหลายครั้งของสัญญาเดียวกันจะส่งคืน _next ที่สร้างขึ้นก่อนหน้านี้เท่านั้น
เนื่องจากพารามิเตอร์ทั้งสองที่ได้รับการยอมรับจากวิธีนี้เป็นทางเลือกและไม่มีข้อ จำกัด เกี่ยวกับประเภทจึงอาจเป็นฟังก์ชันค่าเฉพาะหรือสัญญาอื่น นี่คือการใช้งานเฉพาะของตอนนี้:
Promise.prototype.then = ฟังก์ชั่น (แก้ไข, ปฏิเสธ) {var next = this._next || (this._next = Promise ()); สถานะ var = this.status; var x; if ('รอดำเนินการ' === สถานะ) {isfn (แก้ไข) && this._resolves.push (แก้ไข); isfn (ปฏิเสธ) && this._rejects.push (ปฏิเสธ); กลับมาถัดไป; } if ('แก้ไข' === สถานะ) {ถ้า (! isfn (แก้ไข)) {next.resolve (แก้ไข); } else {ลอง {x = resolve (this.value); Resolvex (ถัดไป, x); } catch (e) {this.reject (e); }} ส่งคืนถัดไป; } if ('ปฏิเสธ' === สถานะ) {ถ้า (! isfn (ปฏิเสธ)) {next.reject (ปฏิเสธ); } else {ลอง {x = ปฏิเสธ (this.reason); Resolvex (ถัดไป, x); } catch (e) {this.reject (e); }} ส่งคืนถัดไป; -ที่นี่ได้ทำให้การใช้งานไลบรารีคลาสสัญญาอื่น ๆ ง่ายขึ้นและการใช้งานนั้นซับซ้อนกว่านี้มากและยังมีฟังก์ชั่นมากขึ้น ตัวอย่างเช่นมีพารามิเตอร์ที่สาม - แจ้งเตือนซึ่งบ่งชี้ถึงความคืบหน้าในปัจจุบันของสัญญาซึ่งมีประโยชน์มากเมื่ออัปโหลดไฟล์การออกแบบ ฯลฯ การประมวลผลของพารามิเตอร์ต่าง ๆ ของตอนนั้นเป็นส่วนที่ซับซ้อนที่สุด นักเรียนที่สนใจสามารถอ้างถึงการใช้ห้องสมุดสัญญาประเภทอื่น ๆ
ควรจำเป็นต้องใช้วิธีการอย่างน้อยสองวิธีอย่างน้อยสองวิธีในการแปลงสถานะของสัญญาจากการรอดำเนินการเพื่อแก้ไขหรือปฏิเสธและเพื่อดำเนินการคิวการโทรกลับที่เกี่ยวข้องคือ resolve() และ reject() วิธีการ
ณ จุดนี้สัญญาง่ายๆได้รับการออกแบบ ต่อไปนี้เป็นการนำไปใช้อย่างง่ายของสองฟังก์ชั่นที่สัญญาไว้ต่อไปนี้:
ฟังก์ชั่นการนอนหลับ (MS) {return function (v) {var p = promise (); settimeout (function () {p.resolve (v);}, ms); กลับ P; - ฟังก์ชั่น getimg (url) {var p = promise (); var img = ภาพใหม่ (); img.onload = function () {p.resolve (นี่); - img.onerror = function (err) {p.reject (err); - img.url = url; return p;}; เนื่องจากตัวสร้างสัญญายอมรับงานอะซิงโครนัสเป็นพารามิเตอร์ getImg จึงสามารถเรียกได้เช่นนี้:
ฟังก์ชั่น getimg (url) {return promise (ฟังก์ชั่น (แก้ไข, ปฏิเสธ) {var img = image ใหม่ (); img.onload = function () {resolve (this);}; img.onerror = function (err) {ปฏิเสธ (err);}; img.url = url;});};};ถัดไป (ช่วงเวลาของการเป็นพยานปาฏิหาริย์) สมมติว่ามีข้อกำหนด BT ในการใช้งาน: รับการกำหนดค่า JSON แบบอะซิงโครนัสแยกวิเคราะห์ข้อมูล JSON และรับรูปภาพภายในจากนั้นโหลดรูปภาพตามลำดับและให้เอฟเฟกต์การโหลดเมื่อไม่มีการโหลดรูปภาพ
ฟังก์ชั่น addimg (img) {$ ('#list') ค้นหา ('> li: last-child'). html (''). ผนวก (img);}; ฟังก์ชั่น prepend () {$ ('<li>'). html ('การโหลด ... getData ('map.json'). จากนั้น (ฟังก์ชั่น (ข้อมูล) {$ ('H4'). html (data.name); return data.list.list.list.dure (ฟังก์ชั่น (สัญญา, รายการ) {กลับสัญญา. Promise.resolve ());}). จากนั้น (sleep (300)). แล้วฟังก์ชั่น () {$ ('#เสร็จแล้ว'). show ();});}; $ ('#run'). on ('คลิก', run);การนอนหลับที่นี่จะเพิ่มขึ้นเพื่อดูเอฟเฟกต์คุณสามารถคลิกเพื่อดูการสาธิต! แน่นอนตัวอย่าง node.js สามารถดูได้ที่นี่
ที่นี่วิธีการคงที่ของ Promise.resolve(v) เพียงส่งคืนสัญญาด้วย V เป็นผลลัพธ์ที่เป็นบวก V ไม่สามารถส่งผ่านได้หรืออาจเป็นฟังก์ชันหรือวัตถุหรือฟังก์ชั่นที่มีวิธีการ then (เช่นนั้น)
วิธีการคงที่ที่คล้ายกัน ได้แก่ Promise.cast(promise) ซึ่งสร้างสัญญาที่มีสัญญาเป็นผลลัพธ์ที่เป็นบวก
Promise.reject(reason) สร้างสัญญาด้วยเหตุผลเป็นผลลัพธ์เชิงลบ
สถานการณ์การใช้งานที่แท้จริงของเราอาจซับซ้อนมากและมักจะต้องใช้งานอะซิงโครนัสหลายงานที่จะดำเนินการแบบกระจายแบบขนานหรืออนุกรม ในเวลานี้คุณสามารถทำส่วนขยายต่าง ๆ เพื่อสัญญาเช่นการดำเนินการตาม Promise.all() ยอมรับคิวสัญญาและรอให้พวกเขาดำเนินการให้เสร็จสมบูรณ์ก่อนดำเนินการต่อและตัวอย่างเช่น Promise.any() เมื่อคิวสัญญาใด ๆ อยู่ในสถานะที่เสร็จสมบูรณ์
คุณสามารถอ้างถึงบทความนี้ใน HTML5Rocks JavaScript สัญญา ปัจจุบันเบราว์เซอร์ขั้นสูงเช่น Chrome และ Firefox มีวัตถุสัญญาในตัวซึ่งให้บริการอินเทอร์เฟซการทำงานมากขึ้นเช่น Promise.all() ซึ่งสนับสนุนการส่งผ่านในอาร์เรย์สัญญาแล้วดำเนินการเมื่อสัญญาทั้งหมดเสร็จสิ้น นอกจากนี้ยังมีข้อยกเว้นที่เป็นมิตรและทรงพลังมากขึ้นซึ่งน่าจะเพียงพอที่จะจัดการกับการเขียนโปรแกรมแบบอะซิงโครนัสทุกวัน
ห้องสมุด JS ยอดนิยมทุกวันนี้มีสัญญาเกือบทั้งหมดที่มีการใช้งานในระดับที่แตกต่างกันเช่น Dojo, JQuery, Zepto, เมื่อ. js, Q, ฯลฯ แต่วัตถุที่ถูกเปิดเผยส่วนใหญ่ Deferred การใช้ jQuery (Zepto คล้ายกัน) เป็นตัวอย่างให้ใช้ getImg() :
ฟังก์ชั่น getimg (url) {var def = $ .deferred (); var img = ภาพใหม่ (); img.onload = function () {def.resolve (นี่); - img.onerror = function (err) {def.reject (err); - img.src = url; return def.promise ();}; แน่นอนว่าใน jQuery การดำเนินการจำนวนมากกลับรอการตัดบัญชีหรือสัญญาเช่น animate และ ajax :
// ajax $ .ajax (ความทึบ ': 0}, 1000) .promise (). จากนั้น (ฟังก์ชั่น () {console.log (' เสร็จสิ้น ');}); // ajax $ .ajax (ตัวเลือก) $ .ajax (ตัวเลือก 2)). จากนั้น (ฟังก์ชั่น () {console.log ('เสร็จสิ้นทั้งหมด');}, ฟังก์ชัน () {console.error ('มีบางอย่างผิดปกติ');}); jQuery ยังใช้วิธี done() และ fail() ซึ่งเป็นทางลัดของวิธีการนั้น
เพื่อจัดการกับสัญญาคิว jQuery ใช้วิธี $.when() และการใช้งานนั้นคล้ายกับ Promise.all()
สำหรับไลบรารีคลาสอื่น ๆ เป็นเรื่องที่ควรกล่าวถึงที่นี่ว่าเมื่อใดที่ js มีรหัสเล็ก ๆ น้อย ๆ มันใช้สัญญาอย่างเต็มที่สนับสนุนเบราว์เซอร์และ node.js และให้ API ที่สมบูรณ์ยิ่งขึ้นซึ่งเป็นตัวเลือกที่ดี เนื่องจากข้อ จำกัด ด้านพื้นที่เราจะไม่ขยายอีกต่อไป
เราเห็นว่าไม่ว่าการดำเนินการตามสัญญาจะซับซ้อนเพียงใดการใช้งานนั้นง่ายมากและรหัสองค์กรนั้นชัดเจนมาก จากนี้ไปไม่จำเป็นต้องถูกทรมานโดยการโทรกลับ
ในที่สุดสัญญาก็สง่างามมาก! แต่สัญญาจะแก้ปัญหาการทำรังลึกของการโทรกลับอย่างลึกซึ้ง มันเป็นเครื่องกำเนิดไฟฟ้าที่ทำให้การเขียนโปรแกรมแบบอะซิงโครนัสง่ายขึ้น ทางด้าน node.js ขอแนะนำให้พิจารณาเครื่องกำเนิดไฟฟ้า
บทความถัดไปเครื่องกำเนิดการศึกษา
ข้อความต้นฉบับของ GitHub: https://github.com/chemdemo/chemdemo.github.io/issues/6