Async JS ภายใต้ประทุน
MDN กำหนดเวิร์กโฟลว์แบบอะซิงโครนัสเป็น
..การเริ่มงานที่อาจใช้เวลานานและยังคงสามารถตอบสนองต่อเหตุการณ์อื่นๆ ในขณะที่งานนั้นทำงาน... เมื่องานนั้นเสร็จสิ้น โปรแกรมของคุณจะแสดงผลลัพธ์
แต่สิ่งนี้สามารถทำได้บนเว็บด้วย JavaScript ได้อย่างไร
ก่อนที่จะดำดิ่งสู่การใช้งาน async การมีความเข้าใจพื้นฐานของ JS ในระดับที่ต่ำกว่าจะช่วยให้
JS ทำงานอย่างไร
JavaScript เป็น single-threaded และตีความ หมายความว่าคำสั่งถูกดำเนินการโดยตรงและไม่มีขั้นตอนการคอมไพล์แบบ ex-ante ยกตัวอย่างเครื่องยนต์ V8 ซึ่งเป็นเครื่องยนต์ยอดนิยม
ในระดับสูง V8 จะแยกวิเคราะห์โค้ด สร้างโครงสร้างไวยากรณ์แบบนามธรรม (AST) และคอมไพล์ให้เป็น bytecode ที่ปรับให้เหมาะสมด้วยล่าม นอกจากนี้ยังมีตัวเตรียมคำ คอมไพเลอร์สองตัว และแคชที่ทำให้ทำงานได้อย่างรวดเร็ว (V8 มีบล็อก ที่ยอดเยี่ยม เกี่ยวกับเรื่องนี้) มัน ให้เฉพาะ call stack, heap และ event loop ( ไม่ใช่จริงๆ ) เราสามารถเพิกเฉยต่อฮีปสำหรับจุดประสงค์ของรูกระต่ายนี้: มันเป็นหน่วยความจำที่ไม่มีโครงสร้างซึ่งมีการอ้างอิงวัตถุและตัวแปร
วนรอบเหตุการณ์รันไทม์
ซึ่งแตกต่างจาก Java หรือ C++, JS อนุญาตให้ดำเนินการเหตุการณ์หนึ่งครั้ง เราสามารถเห็นสิ่งนี้ในเบราว์เซอร์ของเราโดยติดตามเส้นทางการโทรด้วยconsole.trace():
// shoutout to ES6 arrow functions
foo = () => {
console.log("Hello, World!");
bar();
}
bar = () => {
console.log("Goodbye, World!");
trace();
}
trace = () => console.trace();
foo();
Hello, World! VM1049:6
Goodbye, World! VM1049:11
// console.trace
trace @ VM1049:11
bar @ VM1049:7
foo @ VM1049:3
call stack visualization
ตั้งค่าหมดเวลา
มาเรียกใช้วิธีการเดียวกันด้วยการหน่วงเวลาแบบอะซิงโครนัส 5,000 มิลลิวินาทีด้วยsetTimeout().
foo = () => {
console.log("Hello, World!");
setTimeout(bar, 5000);
}
bar = () => {
console.log("Goodbye, World!");
trace();
}
trace = () => console.trace();
foo();
Hello, World! VM1526:2
// 3 days later...
Goodbye, World! VM1526:6
// console.trace
trace @ VM1382:10
bar @ VM1382:6
// setTimeout is invoked
b @ main-base.bundle.UM3…oFn_e5gQ.12.js:1723
setTimeout (async)
Cga.window.setTimeout @ main-base.bundle.UM3…toFn_e5gQ.12.js:516
// foo is pushed to the stack first
foo @ VM1382:3
setTimeoutไม่ได้เป็นส่วนหนึ่งของเอ็นจิ้น JS ซึ่งสมเหตุสมผลเนื่องจากเอ็นจิ้นสามารถจัดการเหตุการณ์ได้ครั้งละหนึ่งเหตุการณ์ อย่างไรก็ตาม เบราว์เซอร์เป็นแบบมัลติเธรดและให้ API เว็บแก่เราเพื่อเข้าถึงการใช้งาน setTimeout ตัวอย่างเช่น Chrome ใช้เครื่องมือ V8 แบบเธรดเดียวในสถาปัตยกรรมแบบหลายกระบวนการ ซึ่งทุกกระบวนการเป็นแบบมัลติเธรดอย่างมาก ในกรณีนี้ เราเรียกสิ่งนี้อย่างชัดเจนด้วยCga.setTimeout @ main-base.bundle.blah-blah-blah...Virtually ซึ่งช่วยให้เราสามารถใช้หลายเธรดได้
วนรอบเหตุการณ์
มาจัดลำดับบล็อกรหัสของเราใหม่:
foo = () => {
console.log("Hello, World!");
setTimeout(bar, 5000);
}
bar = () => {
setTimeout(() => console.log("Goodbye, World!"), 5000);
trace();
}
trace = () => console.trace();
foo();
// output of foo
Hello, World! VM1649:11
// console.trace
trace @ VM1694:11
bar @ VM1694:8
b @ main-base.bundle.UM3…oFn_e5gQ.12.js:1723
setTimeout (async)
Cga.window.setTimeout @ main-base.bundle.UM3…toFn_e5gQ.12.js:516
foo @ VM1694:3
// end trace
// 3 days later...
VM1649:6 Goodbye, World!
JRE รวมถึง:
- JS Engine (เธรดเดียว)
- Java Virtual Machine (JVM) — เครื่องเสมือนที่รันโค้ดในรันไทม์ Btw นี่คือสาเหตุที่ "VM" เพิ่มบันทึกการติดตาม
- API เว็บ
- คิวงาน
- วนรอบเหตุการณ์
call stack ของเครื่องยนต์ของเรากำลังแสดงเหตุการณ์ทีละรายการ เมื่อเห็น setTimeout() เมธอดพร้อมกับการเรียกกลับ (การอ้างอิงฟังก์ชัน) จะถูกมอบหมายให้กับเว็บ API
โปรดสังเกตว่าเอ็นจิ้นไม่สนใจวิธีที่การใช้งาน setTimeout ของ API จัดการกับเหตุการณ์และการเรียกกลับ เอ็นจิ้นจะดำเนินการต่อเพื่อแสดงเหตุการณ์ถัดไปและป้อนไปยัง Pacman (JVM ที่รันโค้ด)
ในระหว่างนี้ API จะใช้เธรดเพื่อรอ 5,000 มิลลิวินาที แล้วส่งต่อไปยังคิวงาน ลูปเหตุการณ์เห็นว่าเหตุการณ์นั้นเข้าแถวและผลักกลับเข้าไปใน call stack ก็ต่อเมื่อ call stack ว่างเปล่า
คุณสามารถใช้วิชวลไลเซอร์ สนุกๆ ของ Philip Roberts เพื่อดูการทำงานทั้งหมด ลองดูการพูดคุย ที่ยอดเยี่ยมของเขา ที่ JSConf ในปี 2014





































![รายการที่เชื่อมโยงคืออะไร? [ส่วนที่ 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)