เหตุใดสัญญา javascript ES6 จึงดำเนินการต่อไปหลังจากการแก้ไข


105

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

ฉันถือว่าการแก้ไขหรือปฏิเสธเป็นเวอร์ชันที่เป็นมิตรกับสิ่งแวดล้อมของ exit หรือ return ซึ่งจะหยุดการทำงานของฟังก์ชันทันทีทั้งหมด

ใครช่วยอธิบายความคิดเบื้องหลังว่าทำไมบางครั้งตัวอย่างต่อไปนี้จึงแสดง console.log หลังจากการโทรแก้ไข:

var call = function() {
    return new Promise(function(resolve, reject) {
        resolve();
        console.log("Doing more stuff, should not be visible after a resolve!");
    });
};

call().then(function() {
    console.log("resolved");
});

jsbin


13
คำถามที่สมเหตุสมผล แต่แล้วอีกครั้ง JS ก็ดำเนินการคำสั่งหนึ่งต่อจากอีกคำหนึ่งตามที่คุณบอก resolve()ไม่ใช่คำสั่งควบคุม JS ที่จะมีผลอย่างน่าอัศจรรย์returnมันเป็นเพียงการเรียกใช้ฟังก์ชันและใช่การดำเนินการจะดำเนินต่อไปหลังจากนั้น

นี่เป็นคำถามที่ดีและแม้ว่าหลังจากอ่านคำตอบทั้งหมดแล้วฉันก็ไม่แน่ใจเกี่ยวกับแนวทางปฏิบัติที่ดีที่สุด ...
Gabriel Glenn

ฉันคิดว่าความเข้าใจผิดมาจากสิ่งที่คุณกำลังยุติด้วยการแก้ไข (): คำสัญญา IS ได้รับการแก้ไขหลังจากที่คุณเรียกใช้การแก้ไข () แต่อย่างที่คนอื่นพูดไปแล้วนี่ไม่ได้หมายความว่าฟังก์ชันที่ยกเลิกสัญญาได้สิ้นสุดลงแล้ว หน้าที่ด้วยดังนั้นจึงดำเนินต่อไปจนกว่าจะถึงการยุติ "ปกติ"
Giuseppe Bertone

คำตอบ:


154

JavaScript มีแนวคิด"run to complete" "ทำงานจะเสร็จสิ้น"ฟังก์ชันจะถูกดำเนินการจนกว่าจะถึงreturnคำสั่งหรือจุดสิ้นสุดเว้นแต่จะเกิดข้อผิดพลาด โค้ดอื่นที่อยู่นอกฟังก์ชันไม่สามารถแทรกแซงได้ (เว้นแต่จะมีข้อผิดพลาดเกิดขึ้นอีกครั้ง)

หากคุณต้องการresolve()ออกจากฟังก์ชัน initialiser คุณต้องนำหน้ามันด้วยreturn:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});

สวัสดีเฟลิกซ์ - ฉันคิดว่านี่เป็นเพียงส่วนหนึ่งของเรื่องราว - ส่วนอื่น ๆresolve()คือมันเป็นฟังก์ชัน async ดังที่เราเห็นในคำตอบอื่น (ที่ถูกลบ) บางคนเชื่อว่าการโทรresolveจะเรียกใช้การโทรกลับทันที
Alnitak

3
@Alnitak resolveเองไม่ตรงกันมันซิงโครนัสอย่างสมบูรณ์ แม้ว่าจะใช้ ES6 API อย่างเคร่งครัด แต่ก็ไม่สามารถสังเกตได้ว่าเป็นแบบซิงโครนัสหรืออะซิงโครนัส
Esailija

2
@Esailija โอเคบางทีฉันก็ไม่ชัดเจน บางคนเชื่อว่าการโทรresolveจะส่งผลให้การโทรกลับที่ลงทะเบียนถูกเรียกใช้ทันทีซึ่งเป็นส่วนหนึ่งของกลุ่มการโทรปัจจุบัน นั่นไม่เป็นความจริง แต่เพียงแค่จัดคิวการโทรกลับแทน (และคุณพูดถูกไม่ใช่ async แต่มันก็ทำตามนั้นและยุติทันที)
Alnitak

@Alnitak: ฉันเข้าใจสิ่งที่คุณพูด ฉันเพิ่งตีความว่าทำไมการconsole.logแสดงที่แทนที่จะแสดงทำไมมันจึงแสดงตามลำดับนั้น ที่ผ่านมาสิ่งที่resolveทำและคำสัญญาไม่เกี่ยวข้องกับวิธีที่ฉันตีความคำถาม แต่แน่นอนว่าสิ่งสำคัญคือต้องรู้ในบริบทของคำสัญญา เหตุผลประการหนึ่งที่ฉันเพิ่มคะแนนให้คำตอบของคุณ :)
Felix Kling

9
@Bergi ในการแก้ไขของคุณคุณพูดว่า "return fix ();" ซึ่งดูเหมือนผิดปกติ เพื่อที่จะโน้มน้าวตัวเองว่าไม่มีความสำคัญอะไรเกิดขึ้นที่นั่นฉันต้องอ่านเอกสารและเห็นว่า (1) การแก้ไข () ไม่ได้ส่งคืนผลลัพธ์ใด ๆ และ (2) ค่าส่งกลับของการเรียกกลับเริ่มต้นไม่ได้ ดูเหมือนจะใช้ จะไม่ชัดเจนกว่าที่จะพูดว่า "แก้ไข (); return;" จึงหลีกเลี่ยงความฟุ้งซ่านนี้?
Don Hatch

19

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

ดังนั้นเมื่อคุณเรียกใช้resolveการโทรกลับจะถูกจัดคิวและการเรียกใช้ฟังก์ชันจะดำเนินต่อไปทันทีโดยมีรหัสใด ๆ ตามหลังการresolve()เรียก

เมื่อลูปเหตุการณ์ JS ได้รับการควบคุมย้อนกลับเท่านั้นที่สามารถลบการเรียกกลับออกจากคิวและเรียกใช้จริงได้


1
การจัดคิวการโทรกลับมีการบันทึกไว้ใน A + Specs หรือใน ES6?
thefourtheye

5
@thefourtheye: ตอนนี้ข้อมูลจำเพาะของเหตุการณ์วนซ้ำเป็นส่วนหนึ่งของHTML5แล้ว ES6 กำหนดวิธีการภายในที่เรียกว่าซึ่งถูกเรียกโดยEnqueueJob .then
Felix Kling

@thefourtheye: จริงๆแล้ว ES6 ก็เหมือนจะกำหนดคิวด้วยนะ: people.mozilla.org/~jorendorff/… . ฉันเดาว่าเกี่ยวข้องกับเหตุการณ์ที่เกิดขึ้นไม่ทางใดก็ทางหนึ่ง
Felix Kling

@FelixKling ขอบคุณสำหรับลิงค์ - ฉันรู้ว่านี่เป็นวิธีการทำงาน แต่ไม่สามารถอ้างถึงบทและกลอนได้
Alnitak

2
@FelixKling เป็น microtasks / macrotasks นี่คือส่วนหนึ่งในข้อมูลจำเพาะที่ "defers" "เมื่อไม่มีบริบทการดำเนินการที่กำลังทำงานอยู่และสแต็กบริบทการดำเนินการว่างเปล่าการใช้ ECMAScript จะลบ PendingJob แรกออกจากคิวงานและใช้ข้อมูลที่มีอยู่ เพื่อสร้างบริบทการดำเนินการและเริ่มการดำเนินการของการดำเนินการนามธรรมของงานที่เกี่ยวข้อง "
Benjamin Gruenbaum
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.