
อะซิงโครนัสคือการเพิ่มอัตราการครอบครอง CPU และทำให้มันยุ่งตลอดเวลา
การดำเนินการบางอย่าง (โดยทั่วไปที่สุดคือ I/O) ไม่ต้องการการมีส่วนร่วมของ CPU และใช้เวลานานมาก หากไม่ได้ใช้แบบอะซิงโครนัส จะทำให้เกิดสถานะการบล็อก ส่งผลให้ CPU ไม่ได้ใช้งานและเพจค้าง
เมื่อการดำเนินการ I/O เกิดขึ้นในสภาพแวดล้อมแบบอะซิงโครนัส CPU จะแยกการทำงานของ I/O ออกไป (ในขณะนี้ I/O จะถูกควบคุมโดยตัวควบคุมอื่นและข้อมูลยังคงถูกส่งอยู่) จากนั้นจึงประมวลผลงานถัดไป รอให้การดำเนินการ I/O เสร็จสิ้น แจ้งให้ CPU ทราบ (การเรียกกลับเป็นวิธีการแจ้งเตือน) ให้กลับมาทำงาน
เนื้อหาหลักของ "JavaScript Asynchronous และ Callback" คือเวลาสิ้นสุดที่ระบุของงานอะซิงโครนัสนั้นไม่แน่นอน เพื่อให้ดำเนินการประมวลผลในภายหลังได้อย่างแม่นยำหลังจากงานอะซิงโครนัสเสร็จสิ้นแล้ว การเรียกกลับจะต้องถูกส่งผ่านไปยังฟังก์ชันอะซิงโครนัส ดังนั้นหลังจาก ทำงานของคุณให้เสร็จสิ้น ดำเนินการต่อด้วยงานต่อไปนี้
แม้ว่าการโทรกลับอาจทำได้ง่ายมากในการใช้งานแบบอะซิงโครนัส แต่ก็สามารถสร้างการโทรกลับนรกได้เนื่องจากการซ้อนหลายรายการ เพื่อหลีกเลี่ยงการเรียกกลับนรก คุณต้องปฏิเสธและเปลี่ยนการเขียนโปรแกรมแบบซ้อนเป็นการเขียนโปรแกรมเชิงเส้น
Promise เป็นทางออกที่ดีที่สุดในการจัดการ callback hell ใน JavaScript
Promise สามารถแปลได้ว่า "promise" เราสามารถสรุปงานแบบอะซิงโครนัสและเรียกมันว่า Promise กล่าวคือ ให้สัญญาและสัญญาว่าจะให้สัญญาณที่ชัดเจนหลังจากงานแบบอะซิงโครนัสสิ้นสุดลง!
ไวยากรณ์ Promise :
ให้สัญญา = new Promise(function(resolve,reject){
// งานแบบอะซิงโครนัส}) ด้วยไวยากรณ์ข้างต้น เราสามารถสรุปงานแบบอะซิงโครนัสเป็น Promise ฟังก์ชันที่ส่งผ่านเมื่อสร้าง Promise เป็นวิธีการจัดการงานแบบอะซิงโครนัสหรือที่เรียกว่า executor ดำเนินการ
resolve และ reject เป็นฟังก์ชันการเรียกกลับที่ JavaScript จัดเตรียมไว้ให้ สามารถเรียกได้เมื่อ executor ทำงานเสร็จสิ้น:
resolve(result) - หากดำเนินการเสร็จสิ้น result จะถูกส่งกลับreject(error) - หาก error ดำเนินการล้มเหลวและ error จะถูกสร้างขึ้นexecutor จะดำเนินการโดยอัตโนมัติทันทีหลังจากสร้าง Promise และสถานะการดำเนินการจะเปลี่ยนสถานะของคุณสมบัติภายในของ Promise :
state - เริ่มแรก pending ดำเนินการ จากนั้นแปลงเป็น fulfilled หลังจากเรียก resolve หรือถูก rejected เมื่อมีการเรียก rejectresult —— undefined ตั้งแต่แรก จากนั้นจะกลายเป็น value หลังจาก resolve(value) หรือกลายเป็น error หลังจากการเรียก rejectfs.readFile ฟังก์ชันแบบอะซิงโครนัส . เราสามารถส่งผ่านมันไปได้ใน executor การ การดำเนินการอ่านไฟล์จะดำเนินการในไฟล์ ดังนั้นจึงห่อหุ้มงานแบบอะซิงโครนัส
รหัสต่อไปนี้สรุปฟังก์ชัน fs.readFile และใช้ resolve(data) เพื่อจัดการกับผลลัพธ์ที่สำเร็จ และ reject(err) เพื่อจัดการกับผลลัพธ์ที่ล้มเหลว
รหัสมีดังนี้:
ให้สัญญา = สัญญาใหม่ ((แก้ไข, ปฏิเสธ) => {
fs.readFile('1.txt', (ผิดพลาด, ข้อมูล) => {
console.log('อ่าน 1.txt')
ถ้า (ผิดพลาด) ปฏิเสธ (ผิดพลาด)
แก้ไข (ข้อมูล)
})}) หากเรารันโค้ดนี้ คำว่า "Read 1.txt" จะถูกส่งออก เพื่อพิสูจน์ว่าการดำเนินการอ่านไฟล์จะดำเนินการทันทีหลังจากสร้าง Promise
โดยทั่วไป
Promiseจะห่อหุ้มโค้ดอะซิงโครนัสภายใน แต่ไม่เพียงห่อหุ้มโค้ดอะซิงโครนัสเท่านั้น
กรณี Promise ข้างต้นจะสรุปการดำเนินการอ่านไฟล์ทันทีหลังจากการสร้างเสร็จสมบูรณ์ หากคุณต้องการได้รับผลลัพธ์ของการดำเนินการ Promise คุณต้องใช้สามวิธี then , catch และ finally
then ของ Promise สามารถใช้เพื่อจัดการงานหลังจากการดำเนินการ Promise เสร็จสิ้น ได้รับพารามิเตอร์การโทรกลับสองตัวดังนี้:
Promise.then(function(result),function(error))
result คือค่าที่ได้รับจาก resolveerror ของพารามิเตอร์คือพารามิเตอร์ที่ได้รับจาก rejectเช่น
:
สัญญา = สัญญาใหม่ ((แก้ไข, ปฏิเสธ) => {
fs.readFile('1.txt', (ผิดพลาด, ข้อมูล) => {
console.log('อ่าน 1.txt')
ถ้า (ผิดพลาด) ปฏิเสธ (ผิดพลาด)
แก้ไข (ข้อมูล)
})})สัญญาแล้ว(
(ข้อมูล) => {
console.log('ดำเนินการสำเร็จ ผลลัพธ์คือ' + data.toString())
-
(ผิดพลาด) => {
console.log('การดำเนินการล้มเหลว ข้อผิดพลาดคือ' + err.message)
}) หากดำเนินการอ่านไฟล์ได้สำเร็จ ฟังก์ชันแรกจะถูกเรียก:
PS E:CodeNodedemos 3-callback> node .index.js อ่าน 1.txt หากดำเนินการสำเร็จ ผลลัพธ์จะเป็น 1
ถูกลบ 1.txt หากดำเนินการล้มเหลว ฟังก์ชันที่สองจะถูกเรียก:
PS E:CodeNodedemos 3-callback> node .index.js อ่าน 1.txt การดำเนินการล้มเหลวโดยมีข้อผิดพลาด ENOENT: ไม่มีไฟล์หรือไดเร็กทอรีดังกล่าว ให้เปิด 'E:CodeNodedemos 3-callback1.txt'
หากเรามุ่งเน้นเฉพาะผลลัพธ์ของการดำเนินการที่ประสบความสำเร็จ เราสามารถส่งผ่านได้เพียงรายการเดียว ฟังก์ชั่นโทรกลับ:
สัญญา .then((data)=>{
console.log('Successfully Executed, the result is' + data.toString())}) ณ จุดนี้ เราได้ปรับใช้การดำเนินการอ่านแบบอะซิงโครนัสของไฟล์
หากเรามุ่งเน้นไปที่ผลลัพธ์ของความล้มเหลวเท่านั้น เราสามารถส่ง null ไปยังค่าแรก then โทรกลับได้: promise.then(null,(err)=>{...})
หรือใช้วิธีที่สง่างามกว่านี้: promise.catch((err)=>{...})
Let Promise = new Promise((resolve, reject) => {
fs.readFile('1.txt', (ผิดพลาด, ข้อมูล) => {
console.log('อ่าน 1.txt')
ถ้า (ผิดพลาด) ปฏิเสธ (ผิดพลาด)
แก้ไข (ข้อมูล)
})})promise.catch((ผิดพลาด)=>{
console.log(err.message)}) .catch((err)=>{...}) and then(null,(err)=>{...}) มีผลเหมือนกันทุกประการ
.finally finally เป็นฟังก์ชันที่จะดำเนินการโดยไม่คำนึงถึงผลลัพธ์ของ promise โดยมีวัตถุประสงค์เดียวกับใน try...catch... ไวยากรณ์ finally และสามารถจัดการการดำเนินการที่ไม่เกี่ยวข้องกับผลลัพธ์ได้
ตัวอย่างเช่น:
new Promise((แก้ไข,ปฏิเสธ)=>{
//something...}).finally(()=>{console.log('Executeโดยไม่คำนึงถึงผลลัพธ์')}).then(result=>{...}, err=>{...} ) การโทรกลับในขั้นสุดท้ายไม่มีพารามิเตอร์ fs.readFile() จะอ่าน 10 ไฟล์ตามลำดับและส่งออกเนื้อหา ของสิบไฟล์ตามลำดับ
เนื่องจาก fs.readFile() เป็นแบบอะซิงโครนัส เราจึงต้องใช้การซ้อนการโทรกลับ รหัสจึงเป็นดังนี้:
fs.readFile('1.txt', (err, data) => {
console.log(data.toString()) //1
fs.readFile('2.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('3.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('4.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('5.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('6.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('7.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('8.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('9.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
fs.readFile('10.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())
// ==> ประตูนรก})
-
-
-
-
-
-
-
})}) แม้ว่าโค้ดข้างต้นจะสามารถทำงานให้เสร็จสิ้นได้ เมื่อการซ้อนการโทรเพิ่มขึ้น ระดับโค้ดก็จะลึกขึ้นและความยากในการบำรุงรักษาก็เพิ่มขึ้น โดยเฉพาะอย่างยิ่งเมื่อเราใช้โค้ดจริงที่อาจมีหลายลูปและคำสั่งแบบมีเงื่อนไข แทนที่จะเป็น อย่างง่าย console.log(...) ในตัวอย่าง
ถ้าเราไม่ใช้การเรียกกลับและเรียก fs.readFile() โดยตรงตามลำดับตามโค้ดต่อไปนี้ จะเกิดอะไรขึ้น?
//หมายเหตุ: นี่เป็นวิธีที่ผิดในการเขียน fs.readFile('1.txt', (err, data) => {
console.log(data.toString())})fs.readFile('2.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())})fs.readFile('3.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())})fs.readFile('4.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())})fs.readFile('5.txt', (ผิดพลาด, ข้อมูล) => {
console.log(data.toString())})fs.readFile('6.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())})fs.readFile('7.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())})fs.readFile('8.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())})fs.readFile('9.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())})fs.readFile('10.txt', (ผิดพลาด ข้อมูล) => {
console.log(data.toString())}) ต่อไปนี้คือผลลัพธ์ของการทดสอบของฉัน (ผลลัพธ์ของการดำเนินการแต่ละครั้งแตกต่างกัน):
PS E:CodeNodedemos 3-callback> node .indexเหตุผลที่
js12346957108
สร้างผลลัพธ์ที่ไม่ต่อเนื่องกันนี้ เป็นแบบอะซิงโครนัส ไม่ใช่แบบมัลติเธรดแบบอะซิงโครนัสสามารถทำได้ในเธรดเดียว
เหตุผลที่ใช้กรณีข้อผิดพลาดนี้เพื่อเน้นแนวคิดเรื่องอะซิงโครนัส หากคุณไม่เข้าใจว่าทำไมผลลัพธ์นี้จึงเกิดขึ้น คุณต้องกลับไปชดเชยบทเรียน!
แนวคิดในการใช้ Promise เพื่อแก้ปัญหาการอ่านไฟล์ตามลำดับแบบอะซิงโครนัส:
promise1 และใช้ resolve เพื่อส่งคืนผลลัพธ์promise1.then เพื่อรับและส่งออกผลลัพธ์การอ่านไฟล์promise2 ใหม่เพื่อ
promise1.thenpromise2.then ส่งpromise3 promise2.thenpromise3.thenรหัสมีดังนี้:
ให้สัญญา 1 = สัญญาใหม่ ( (แก้ไข, ปฏิเสธ) => {
fs.readFile('1.txt', (ผิดพลาด, ข้อมูล) => {
ถ้า (ผิดพลาด) ปฏิเสธ (ผิดพลาด)
แก้ไข (ข้อมูล)
})})ให้สัญญา2 =สัญญา1.แล้ว(
ข้อมูล => {
console.log(data.toString())
คืนสัญญาใหม่ ((แก้ไข, ปฏิเสธ) => {
fs.readFile('2.txt', (ผิดพลาด, ข้อมูล) => {
ถ้า (ผิดพลาด) ปฏิเสธ (ผิดพลาด)
แก้ไข (ข้อมูล)
-
-
})ให้สัญญา3 =สัญญา2.แล้ว(
ข้อมูล => {
console.log(data.toString())
คืนสัญญาใหม่ ((แก้ไข, ปฏิเสธ) => {
fs.readFile('3.txt', (ผิดพลาด, ข้อมูล) => {
ถ้า (ผิดพลาด) ปฏิเสธ (ผิดพลาด)
แก้ไข (ข้อมูล)
-
-
})ให้สัญญา4 = สัญญา3.แล้ว(
ข้อมูล => {
console.log(data.toString())
-
})... ... ด้วยวิธีนี้เราเขียน callback hell ดั้งเดิมที่ซ้อนกันเป็นโหมดเชิงเส้น
แต่ยังคงมีปัญหากับโค้ดอยู่ แม้ว่าโค้ดจะสวยงามมากขึ้นในแง่ของการจัดการ แต่ก็ทำให้ความยาวของโค้ดเพิ่มขึ้นอย่างมาก
โค้ดด้านบนยาวเกินไป เราสามารถลดจำนวนโค้ดได้ด้วยสองขั้นตอน:
promise ระดับกลาง และลิงก์ .thenรหัสดังต่อไปนี้:
ฟังก์ชั่น myReadFile (เส้นทาง) {
คืนสัญญาใหม่ ((แก้ไข, ปฏิเสธ) => {
fs.readFile (เส้นทาง (ผิดพลาด ข้อมูล) => {
ถ้า (ผิดพลาด) ปฏิเสธ (ผิดพลาด)
console.log(data.toString())
แก้ไข()
-
})}myReadFile('1.txt')
.then(data => { return myReadFile('2.txt') })
.then(data => { return myReadFile('3.txt') })
.then(data => { return myReadFile('4.txt') })
.then(data => { return myReadFile('5.txt') })
.then(data => { return myReadFile('6.txt') })
.then(data => { return myReadFile('7.txt') })
.then(data => { return myReadFile('8.txt') })
.then(data => { return myReadFile('9.txt') })
.then(data => { return myReadFile('10.txt') }) เนื่องจาก เมธอด myReadFile จะส่งคืน Promise ใหม่ เราจึงสามารถดำเนินการเมธอด . .then ได้โดยตรง
ผลลัพธ์การเรียกใช้โค้ดจะเป็นดังนี้:
PS E:CodeNodedemos 3-callback> node .index.js12345678910
การดำเนินการอ่านไฟล์แบบอะซิงโครนัสและตามลำดับเสร็จสมบูรณ์
หมายเหตุ: ต้องส่งคืนออบเจ็กต์
Promiseใหม่ด้วยเมธอด.thenของแต่ละขั้นตอน มิฉะนั้น จะได้รับPromiseเก่าก่อนหน้านี้เนื่องจากแต่ละ
thenจะยังคงผ่านPromiseต่อไป