JavaScript แบบซิงโครนัสเมื่อใด


202

ฉันอยู่ภายใต้การแสดงผลของ JavaScript นั้นแบบอะซิงโครนัสเสมอ อย่างไรก็ตามฉันได้เรียนรู้ว่ามีสถานการณ์ที่มันไม่ได้ (เช่นการโกง DOM) มีการอ้างอิงที่ดีทุกที่เกี่ยวกับเวลาที่จะซิงโครนัสและเมื่อใดที่มันจะไม่ตรงกัน? jQuery มีผลกับเรื่องนี้หรือไม่?


14
เสมอด้วยข้อยกเว้นของอาแจ็กซ์
defau1t

คำตอบที่ยอมรับนั้นผิดและทำให้เข้าใจผิดกรุณาตรวจสอบ
Suraj Jain

2
ก็ยังมีประโยชน์ดูyoutube.com/watch?v=8aGhZQkoFbQจะเข้าใจห่วงเหตุการณ์และวิธีการที่กอง APIs เว็บและการทำงานที่คิวงานที่เกี่ยวกับการซิงค์และ async
mtpultz

1
@ defau1t ไม่ผิดจาวาสคริปต์มักจะซิงโครนัสเมื่อการโทร ajax เสร็จสิ้นการติดต่อกลับจะสิ้นสุดลงในคิวมันเป็นข้อยกเว้นของลักษณะซิงโครนัสของจาวาสคริปต์
Suraj Jain

คำตอบ:


281

JavaScript มีการซิงโครนัสและเธรดเดียวเสมอ หากคุณกำลังรันบล็อกโค้ด JavaScript บนหน้าเว็บจะไม่มีการเรียกใช้ JavaScript อื่นในหน้านั้นอีก

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

ตัวจับเวลาจาวาสคริปต์ทำงานด้วยการเรียกกลับแบบเดียวกัน

การอธิบาย JavaScript แบบอะซิงโครนัสอาจทำให้เข้าใจผิด มันแม่นยำยิ่งกว่าที่จะบอกว่า JavaScript นั้นมีการซิงโครนัสและเธรดเดี่ยวพร้อมกลไกการโทรกลับที่หลากหลาย

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


31
ขออภัยฉันไม่เข้าใจคำสั่งนี้มาก "รหัสจะหยุดดำเนินการจนกว่าการโทรจะส่งกลับ (สำเร็จหรือผิดพลาด)" คุณช่วยอธิบายได้ไหม คำสั่งนั้นจะเป็นจริงได้อย่างไรเมื่อคุณพูดว่า "คำสั่งนั้นจะไม่ขัดจังหวะรหัสอื่นที่กำลังทำงานอยู่"; คุณกำลังพูดถึงรหัสโทรกลับในคำสั่งแรกเท่านั้นหรือ โปรดให้ความกระจ่างแก่ฉัน
กฤษณะ

2
Nettuts มีบทช่วยสอนที่ค่อนข้างดีในการอธิบายพื้นฐานของ async ที่นี่: net.tutsplus.com/tutorials/javascript-ajax/…
RobW

26
@cletus คำสั่ง "รหัสจะหยุดดำเนินการจนกว่าการโทรกลับมา" ต้องการการแก้ไขเนื่องจากการดำเนินการไม่หยุด การเรียกใช้โค้ดสามารถดำเนินการต่อได้ มิฉะนั้นจะหมายถึงการโทรนั้นเป็นแบบซิงโครนัส
HS

1
ฉันไม่เข้าใจข้อความนั้น
Towry

12
คำตอบนี้ทำให้เข้าใจผิดและสับสนอย่างไม่น่าเชื่อ โปรดดูคำตอบของ CMS 'หรือ Faraz Ahmad แทน
iono

214

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

บริบทการดำเนินการ

เอ็นจิ้น JavaScript โต้ตอบกับเอนจิ้นอื่น ๆ ในเบราว์เซอร์ ในกองการประมวลผล JavaScript มีบริบทส่วนกลางที่ด้านล่างและเมื่อเราเรียกใช้ฟังก์ชันเครื่องยนต์ JavaScript จะสร้างบริบทการดำเนินการใหม่สำหรับฟังก์ชั่นที่เกี่ยวข้อง เมื่อฟังก์ชั่นที่เรียกใช้ออกจากบริบทการดำเนินการของมันจะโผล่ขึ้นมาจากสแต็คจากนั้นบริบทการดำเนินการต่อไปคือผุดและอื่น ๆ ...

ตัวอย่างเช่น

function abc()
{
   console.log('abc');
}


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();

ในโค้ดด้านบนบริบทการดำเนินการทั่วโลกจะถูกสร้างขึ้นและในบริบทนี้var oneจะถูกเก็บไว้และค่าของมันจะเป็น 1 ... เมื่อเรียก xyz () ถูกเรียกจากนั้นบริบทการดำเนินการใหม่จะถูกสร้างขึ้นและหากเรากำหนดตัวแปรใด ๆ ในฟังก์ชัน xyz ตัวแปรเหล่านั้นจะถูกเก็บไว้ในบริบทการดำเนินการของ xyz () ในฟังก์ชั่น xyz เราเรียกใช้ abc () จากนั้นบริบทการดำเนินการ abc () จะถูกสร้างและวางในสแต็กการดำเนินการ ... ตอนนี้เมื่อ abc () เสร็จสิ้นบริบทของมันจะถูกผุดจากสแต็กดังนั้นบริบท xyz () สแต็คและบริบททั่วโลกจะถูกตอก ...

ตอนนี้เกี่ยวกับการเรียกกลับไม่ตรงกัน; อะซิงโครนัสหมายถึงมากกว่าหนึ่งครั้ง

เช่นเดียวกับสแต็คการดำเนินการมีคิวงานอีเว้นท์ เมื่อเราต้องการได้รับแจ้งเกี่ยวกับเหตุการณ์บางอย่างในเอ็นจิน JavaScript เราสามารถฟังเหตุการณ์นั้นและเหตุการณ์นั้นถูกวางในคิว ตัวอย่างเช่นเหตุการณ์คำขอ Ajax หรือเหตุการณ์คำขอ HTTP

เมื่อใดก็ตามที่การประมวลผลสแต็กต์ว่างเปล่าดังแสดงในตัวอย่างโค้ดด้านบนเอ็นจิ้น JavaScript จะดูคิวเหตุการณ์เป็นระยะและดูว่ามีเหตุการณ์ใด ๆ ที่ต้องได้รับการแจ้งเตือน ตัวอย่างเช่นในคิวมีสองเหตุการณ์คำขอ ajax และคำขอ HTTP นอกจากนี้ยังดูว่ามีฟังก์ชันที่จำเป็นต้องเรียกใช้ในทริกเกอร์เหตุการณ์นั้นหรือไม่ ... ดังนั้นเครื่องมือ JavaScript จะได้รับแจ้งเกี่ยวกับเหตุการณ์และรู้หน้าที่ที่เกี่ยวข้องเพื่อดำเนินการกับเหตุการณ์นั้น ... ดังนั้นเครื่องมือ JavaScript จึงเรียกใช้ ฟังก์ชั่นการจัดการในกรณีตัวอย่างเช่น AjaxHandler () จะถูกเรียกและชอบเสมอเมื่อฟังก์ชั่นที่ถูกเรียกใช้บริบทการดำเนินการของมันจะถูกวางไว้ในบริบทการดำเนินการและตอนนี้การดำเนินการฟังก์ชั่นเสร็จสิ้นและคำขอ ajax เหตุการณ์ ... เมื่อ AjaxHandler () เสร็จสิ้นการประมวลผลสแต็กจะว่างเปล่าดังนั้นเอ็นจินจะดูคิวเหตุการณ์อีกครั้งและเรียกใช้ฟังก์ชันตัวจัดการเหตุการณ์ของคำขอ HTTP ซึ่งอยู่ในคิวถัดไป เป็นสิ่งสำคัญที่ต้องจำไว้ว่าคิวเหตุการณ์จะถูกประมวลผลเฉพาะเมื่อสแตกการเรียกใช้งานว่าง

ตัวอย่างเช่นดูรหัสด้านล่างอธิบายการประมวลผลสแต็กและการจัดการคิวเหตุการณ์โดยเครื่องมือ Javascript

function waitfunction() {
    var a = 5000 + new Date().getTime();
    while (new Date() < a){}
    console.log('waitfunction() context will be popped after this line');
}

function clickHandler() {
    console.log('click event handler...');   
}

document.addEventListener('click', clickHandler);


waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

และ

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>

ตอนนี้เรียกใช้เว็บเพจและคลิกที่หน้าและดูผลลัพธ์บนคอนโซล ผลลัพธ์จะเป็น

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

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

ดังนั้น JavaScript จึงซิงโครไนซ์เสมอ


16
คำตอบนี้ชัดเจนมากควรเพิ่ม upvotes ให้มากขึ้น
ranu

7
คำอธิบายที่ดีที่สุดแน่นอนสำหรับพฤติกรรมอะซิงโครนัสของฉันได้อ่าน
Charles Jaimet

1
คำอธิบายที่ดีของบริบทการดำเนินการและคิว
Divyanshu Maithani

1
ของหลักสูตรนี้คุณต้องอ่านเล็กน้อยเกี่ยวกับบริบทการดำเนินการและเพียงการเพิ่มของมันว่างเปล่าและในที่สุดคิวเหตุการณ์ทำให้ฉันรู้สึกว่าฉันเข้าใจจาวาสคริปต์ excution อย่างแน่นอน สิ่งที่แย่กว่านั้นคือฉันรู้สึกว่าใช้เวลาอ่านหน้าเดียว แต่ฉันพบว่าแทบจะทุกที่ เหตุใดจึงไม่มีใครพูดได้ พวกเขาไม่รู้หรืออะไร? แต่ฉันรู้สึกว่าการสอน js มีสิ่งนี้มันสามารถช่วยฉันได้หลายครั้ง >: |
ประดิษฐ์จอมพล

2
คำอธิบายที่สมบูรณ์แบบ!
Julsy

100

JavaScript เป็นแบบเธรดเดียวและตลอดเวลาที่คุณทำงานกับการเรียกใช้โค้ดไหลแบบซิงโครนัสปกติ

ตัวอย่างที่ดีของพฤติกรรมแบบอะซิงโครนัสที่ JavaScript สามารถมีได้คือเหตุการณ์ (การโต้ตอบผู้ใช้ผลลัพธ์คำขอ Ajax ฯลฯ ) และตัวจับเวลาโดยทั่วไปการกระทำที่อาจเกิดขึ้นได้ตลอดเวลา

ฉันขอแนะนำให้คุณดูบทความต่อไปนี้:

บทความนั้นจะช่วยให้คุณเข้าใจธรรมชาติของเธรดเดียวของ JavaScript และการทำงานของตัวจับเวลาภายในและการทำงานของ JavaScript แบบอะซิงโครนัส

async


คำตอบที่เข้าใจผิดเราสามารถทำอะไรได้บ้างในกรณีนั้น /
Suraj Jain

8

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

JS ซิงโครนัสในวิธีการใช้รหัส แต่ละบรรทัดจะทำงานหลังจากบรรทัดก่อนที่จะเสร็จสมบูรณ์และถ้าบรรทัดนั้นเรียกใช้ฟังก์ชันหลังจากนั้นจะเสร็จสมบูรณ์ ect ...

จุดหลักของความสับสนเกิดขึ้นจากข้อเท็จจริงที่ว่าเบราว์เซอร์ของคุณสามารถบอก JS ให้เพิ่มรหัสได้ตลอดเวลา (simmlar ถึงวิธีที่คุณสามารถเพิ่มรหัส JS เพิ่มเติมในหน้าจากคอนโซล) ตัวอย่างเช่น JS มีฟังก์ชันการโทรกลับที่มีวัตถุประสงค์เพื่ออนุญาตให้ JS BEHAVE แบบอะซิงโครนัสเพื่อให้ส่วนอื่น ๆ ของ JS สามารถทำงานได้ในขณะที่รอฟังก์ชั่น JS ที่ถูกดำเนินการ (IE GETโทร) เพื่อกลับคำตอบ เบราว์เซอร์มีคำตอบ ณ จุดนั้นลูปเหตุการณ์ (เบราว์เซอร์) จะดำเนินการโค้ด JS ที่เรียกใช้ฟังก์ชันการโทรกลับ

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

ฉันหวังว่านี่จะชัดเจนพอที่จะเป็นประโยชน์กับใครบางคน


4

คำนิยาม

คำว่า "อะซิงโครนัส" สามารถใช้ในความหมายที่แตกต่างกันเล็กน้อยทำให้เกิดคำตอบที่ขัดแย้งกันดูเหมือนที่นี่ในขณะที่พวกเขาไม่ได้จริง Wikipedia บน Asynchronyมีคำจำกัดความนี้:

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

รหัสที่ไม่ใช่ JavaScript สามารถจัดคิวกิจกรรม "นอก" ดังกล่าวต่อคิวเหตุการณ์ของ JavaScript บางส่วน แต่นั่นก็เท่าที่มันจะไป

ไม่มีใบจอง

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

ตัวอย่างเช่นคุณสามารถมั่นใจได้อย่างแน่นอนว่าจะไม่มีการเรียกใช้ JavaScript (ในสคริปต์เดียวกัน) ในขณะที่โค้ดต่อไปนี้กำลังทำงาน:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
    sum += a[i];
}

กล่าวอีกนัยหนึ่งไม่มีการจองล่วงหน้าใน JavaScript สิ่งที่อาจจะอยู่ในคิวเหตุการณ์การประมวลผลเหตุการณ์เหล่านั้นจะต้องรอจนกว่าโค้ดดังกล่าวจะทำงานจนเสร็จ ข้อกำหนด EcmaScript ระบุไว้ในหัวข้อ 8.4 งานและคิวงาน :

การเรียกใช้งานสามารถเริ่มต้นได้เฉพาะเมื่อไม่มีบริบทการดำเนินการที่กำลังดำเนินอยู่และสแต็กบริบทการดำเนินการว่างเปล่า

ตัวอย่างของ Asynchrony

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

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

  • fetch(): เอเจนต์จะใช้ฟังก์ชัน OS เพื่อดำเนินการตามคำขอ HTTP และตรวจสอบการตอบสนองขาเข้าใด ๆ อีกครั้งงานที่ไม่ใช่ JavaScript นี้อาจทำงานคู่ขนานกับรหัส JavaScript บางอย่างที่ยังคงทำงานอยู่ แต่ขั้นตอนการแก้ปัญหาสัญญาซึ่งจะแก้ไขสัญญาที่ส่งคืนโดยfetch()สามารถดำเนินการได้เมื่อ JavaScript ที่ดำเนินการอยู่ในปัจจุบันเสร็จสิ้นลงเท่านั้น

  • requestAnimationFrame(): เอ็นจิ้นการแสดงผลของเบราว์เซอร์ (ที่ไม่ใช่ JavaScript) จะวางเหตุการณ์ในคิว JavaScript เมื่อพร้อมที่จะทำการทาสี เมื่อประมวลผลเหตุการณ์ JavaScript แล้วฟังก์ชันการเรียกกลับจะดำเนินการ

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

มีตัวอย่างอีกมากมาย แต่ฟังก์ชั่นเหล่านี้มีให้โดยสภาพแวดล้อมของโฮสต์ไม่ใช่โดย Core EcmaScript มีแกน ECMAScript Promise.resolve()คุณพร้อมสามารถวางเหตุการณ์ในคิวสัญญาอาชีพกับ

โครงสร้างภาษา

ECMAScript ให้โครงสร้างหลายภาษาให้การสนับสนุนรูปแบบ asynchrony เช่นyield, ,async awaitแต่ปล่อยให้ไม่มีข้อผิดพลาด: ไม่มีรหัส JavaScript จะถูกขัดจังหวะโดยเหตุการณ์ภายนอก "การหยุดชะงัก" ว่าyieldและawaitดูเหมือนจะให้เป็นเพียงการควบคุมวิธีการที่กำหนดไว้ล่วงหน้าของการกลับมาจากการเรียกฟังก์ชั่นและฟื้นฟูบริบทการปฏิบัติของตนในภายหลังอย่างใดอย่างหนึ่งตามรหัส JS (ในกรณีของyield) หรือคิวเหตุการณ์ (ในกรณีของawait)

การจัดการเหตุการณ์ DOM

เมื่อรหัส JavaScript เข้าถึง DOM API สิ่งนี้อาจทำให้ DOM API ทริกเกอร์การแจ้งเตือนแบบซิงโครนัสหนึ่งรายการขึ้นไป และหากรหัสของคุณมีตัวจัดการเหตุการณ์ที่ฟังอยู่มันจะถูกเรียก

สิ่งนี้อาจเกิดขึ้นพร้อมกันเป็น pre-emptive concurrency แต่ไม่ใช่: เมื่อตัวจัดการเหตุการณ์ของคุณส่งคืน DOM API จะกลับมาในที่สุดก็จะกลับมาและรหัส JavaScript เดิมจะดำเนินการต่อ

ในกรณีอื่น ๆ DOM API จะส่งเหตุการณ์ในคิวเหตุการณ์ที่เหมาะสมและ JavaScript จะรับมันเมื่อสแต็คการโทรว่างเปล่า

ดูเหตุการณ์แบบซิงโครนัสและแบบอะซิงโครนัส


0

เป็นแบบซิงโครนัสในทุกกรณี

ตัวอย่างการบล็อกเธรดด้วยPromises:

  const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');

ผลลัพธ์จะเป็น:

Test start...
Test finish...
Finish!
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.