อะซิงโครนัสคือการเพิ่มอัตราการครอบครอง 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
เมื่อมีการเรียก reject
result
—— undefined
ตั้งแต่แรก จากนั้นจะกลายเป็น value
หลังจาก resolve(value)
หรือกลายเป็น error
หลังจากการเรียก reject
fs.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
คือค่าที่ได้รับจาก resolve
error
ของพารามิเตอร์คือพารามิเตอร์ที่ได้รับจาก 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.then
promise2.then
ส่งpromise3
promise2.then
promise3.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
ต่อไป