ฟังก์ชัน Async ช่วยให้คุณเขียนโค้ดที่อิงตามสัญญาได้ราวกับว่าโค้ดเป็นแบบซิงโครนัส
ฟังก์ชัน Async จะเปิดใช้โดยค่าเริ่มต้นใน Chrome, Edge, Firefox และ Safari และฟังก์ชันเหล่านี้ยอดเยี่ยมมาก ซึ่งช่วยให้คุณเขียนโค้ดที่อิงตามสัญญาได้ราวกับว่าโค้ดเป็นแบบซิงโครนัส แต่ไม่บล็อกเธรดหลัก ซึ่งทำให้โค้ดแบบไม่พร้อมกัน "ฉลาด" น้อยลงและอ่านง่ายขึ้น
ฟังก์ชัน Async ทํางานดังนี้
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
หากใช้คีย์เวิร์ด async
ก่อนการนิยามฟังก์ชัน คุณจะใช้ await
ภายในฟังก์ชันได้ เมื่อคุณ await
พรอมต์ ฟังก์ชันจะหยุดชั่วคราวในลักษณะที่ไม่บล็อกจนกว่าพรอมต์จะเสร็จสมบูรณ์ หากสัญญาเป็นจริง คุณจะได้รับมูลค่าคืน หาก Promise ปฏิเสธ ระบบจะแสดงค่าที่ปฏิเสธ
การสนับสนุนเบราว์เซอร์
ตัวอย่าง: การบันทึกการดึงข้อมูล
สมมติว่าคุณต้องการดึงข้อมูล URL และบันทึกการตอบกลับเป็นข้อความ ตัวอย่างการใช้ Promise มีดังนี้
function logFetch(url) {
return fetch(url)
.then((response) => response.text())
.then((text) => {
console.log(text);
})
.catch((err) => {
console.error('fetch failed', err);
});
}
และนี่คือการดำเนินการเดียวกันโดยใช้ฟังก์ชันที่ทำงานแบบแอสซิงค์
async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
} catch (err) {
console.log('fetch failed', err);
}
}
บรรทัดมีจำนวนเท่าเดิม แต่ไม่มีคอลแบ็กทั้งหมดแล้ว วิธีนี้ช่วยให้อ่านได้ง่ายขึ้นมาก โดยเฉพาะสำหรับผู้ที่ไม่คุ้นเคยกับคำมั่นสัญญา
ค่าที่แสดงผลแบบไม่พร้อมกัน
ฟังก์ชัน Async จะแสดงผลพรอมต์เสมอ ไม่ว่าคุณจะใช้ await
หรือไม่ก็ตาม Promise นั้นจะยุติด้วยผลลัพธ์ที่ฟังก์ชัน Async แสดง หรือปฏิเสธด้วยสิ่งที่ฟังก์ชัน Async แสดง ดังนั้น เมื่อใช้
// wait ms milliseconds
function wait(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function hello() {
await wait(500);
return 'world';
}
…การเรียกใช้ hello()
จะแสดงผลลัพธ์ที่เป็นสัญญาที่ดำเนินการด้วย "world"
async function foo() {
await wait(500);
throw Error('bar');
}
…การเรียกใช้ foo()
จะแสดงผลลัพธ์เป็นสัญญาที่ปฏิเสธด้วย Error('bar')
ตัวอย่าง: สตรีมมิงคำตอบ
ประโยชน์ของฟังก์ชันแบบแอสซิงค์จะเพิ่มขึ้นในตัวอย่างที่ซับซ้อนมากขึ้น สมมติว่าคุณต้องการสตรีมการตอบกลับขณะบันทึกข้อมูลไปยังส่วนต่างๆ และแสดงขนาดสุดท้าย
ตัวอย่างคำสัญญามีดังนี้
function getResponseSize(url) {
return fetch(url).then((response) => {
const reader = response.body.getReader();
let total = 0;
return reader.read().then(function processResult(result) {
if (result.done) return total;
const value = result.value;
total += value.length;
console.log('Received chunk', value);
return reader.read().then(processResult);
});
});
}
ผมชื่อ Jake "ผู้ทำตามสัญญา" Archibald เห็นว่าฉันเรียกใช้วิธีนี้อย่างไร
processResult()
ในตัวเองเพื่อตั้งค่าลูปแบบไม่พร้อมกัน การเขียนที่ทำให้ฉันรู้สึกฉลาดมาก แต่เช่นเดียวกับโค้ด "อัจฉริยะ" ส่วนใหญ่ คุณต้องจ้องมองโค้ดนี้นานๆ เพื่อดูว่าโค้ดกำลังทำอะไรอยู่ คล้ายกับภาพภาพลวงตาจากยุค 90
ลองทำอีกครั้งด้วยฟังก์ชันที่ทำงานแบบแอสซิงค์
async function getResponseSize(url) {
const response = await fetch(url);
const reader = response.body.getReader();
let result = await reader.read();
let total = 0;
while (!result.done) {
const value = result.value;
total += value.length;
console.log('Received chunk', value);
// get the next result
result = await reader.read();
}
return total;
}
ความเป็น "อัจฉริยะ" ทั้งหมดหายไปแล้ว เราได้แทนที่ลูปแบบแอซิงโครนัสที่ทำให้ฉันรู้สึกดีใจด้วยลูป while ที่น่าเบื่อแต่เชื่อถือได้ ดีกว่ามาก ในอนาคต คุณจะได้รับตัวดำเนินการแบบแอสซิงค์ ซึ่งจะแทนที่ลูป while
ด้วยลูป for-of ทําให้โค้ดเรียบร้อยยิ่งขึ้น
ไวยากรณ์ของฟังก์ชันที่ทำงานพร้อมกันอื่นๆ
เราได้แสดง async function() {}
ให้คุณแล้ว แต่คีย์เวิร์ด async
สามารถใช้กับไวยากรณ์ของฟังก์ชันอื่นๆ ต่อไปนี้ได้
ฟังก์ชันลูกศร
// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
const response = await fetch(url);
return response.json();
});
เมธอดออบเจ็กต์
const storage = {
async getAvatar(name) {
const cache = await caches.open('avatars');
return cache.match(`/avatars/${name}.jpg`);
}
};
storage.getAvatar('jaffathecake').then(…);
เมธอดของคลาส
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jaffathecake').then(…);
ระวัง หลีกเลี่ยงการเรียงลำดับมากเกินไป
แม้ว่าคุณจะเขียนโค้ดที่ดูเป็นแบบซิงค์ แต่อย่าลืมใช้โอกาสนี้ทำสิ่งต่างๆ พร้อมกัน
async function series() {
await wait(500); // Wait 500ms…
await wait(500); // …then wait another 500ms.
return 'done!';
}
การดำเนินการข้างต้นใช้เวลา 1, 000 มิลลิวินาที ขณะที่
async function parallel() {
const wait1 = wait(500); // Start a 500ms timer asynchronously…
const wait2 = wait(500); // …meaning this timer happens in parallel.
await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
return 'done!';
}
การดำเนินการข้างต้นใช้เวลา 500 มิลลิวินาทีจึงจะเสร็จสมบูรณ์ เนื่องจากทั้ง 2 รายการรอพร้อมกัน มาดูตัวอย่างการใช้งานจริงกัน
ตัวอย่าง: การแสดงผลการดึงข้อมูลตามลําดับ
สมมติว่าคุณต้องการดึงข้อมูลชุด URL และบันทึกโดยเร็วที่สุดตามลําดับที่ถูกต้อง
หายใจเข้าลึกๆ - ตัวอย่างการใช้คำมั่นสัญญามีดังนี้
function markHandled(promise) {
promise.catch(() => {});
return promise;
}
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map((url) => {
return markHandled(fetch(url).then((response) => response.text()));
});
// log them in order
return textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise).then((text) => console.log(text));
}, Promise.resolve());
}
ใช่ ฉันใช้ reduce
เพื่อต่อเชื่อมลำดับของ Promise ฉันฉลาดมาก แต่นี่เป็นการเขียนโค้ดที่ฉลาดมากซึ่งคุณควรหลีกเลี่ยง
อย่างไรก็ตาม เมื่อแปลงโค้ดข้างต้นเป็นฟังก์ชันที่ทำงานแบบไม่พร้อมกัน คุณอาจเรียงลำดับการทำงานมากเกินไป
async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }
function markHandled(...promises) { Promise.allSettled(promises); } async function logInOrder(urls) { // fetch all the URLs in parallel const textPromises = urls.map(async (url) => { const response = await fetch(url); return response.text(); }); markHandled(...textPromises); // log them in sequence for (const textPromise of textPromises) { console.log(await textPromise); } }
วิธีแก้ปัญหาการรองรับเบราว์เซอร์: เครื่องมือสร้าง
หากกําหนดเป้าหมายเบราว์เซอร์ที่รองรับ Generator (ซึ่งรวมถึงเบราว์เซอร์หลักทุกเวอร์ชันล่าสุด) คุณจะประมาณการ Polyfill ฟังก์ชันแบบแอสซิงค์ได้
Babel จะดำเนินการนี้ให้คุณ ดูตัวอย่างผ่าน Babel REPL
- โปรดสังเกตความคล้ายคลึงของโค้ดที่แปลงแล้ว การเปลี่ยนรูปแบบนี้เป็นส่วนหนึ่งของค่ากําหนดล่วงหน้า es2017 ของ Babel
เราขอแนะนำให้ใช้วิธีการแปลง เนื่องจากคุณสามารถปิดได้เมื่อเบราว์เซอร์เป้าหมายรองรับฟังก์ชันการทำงานแบบแอสซิงค์ แต่หากไม่ต้องการใช้โปรแกรมแปลง คุณสามารถนําโพลีไฟล์ของ Babel ไปใช้เองได้ แทนที่จะใช้
async function slowEcho(val) {
await wait(1000);
return val;
}
…คุณจะต้องใส่ polyfill และเขียนดังนี้
const slowEcho = createAsyncFunction(function* (val) {
yield wait(1000);
return val;
});
โปรดทราบว่าคุณต้องส่ง Generator (function*
) ไปยัง createAsyncFunction
และต้องใช้ yield
แทน await
นอกเหนือจากนั้น อุปกรณ์จะทำงานเหมือนเดิม
วิธีแก้ปัญหา: regenerator
หากคุณกําหนดเป้าหมายเป็นเบราว์เซอร์รุ่นเก่า Babel จะเปลี่ยนรูปแบบ Generator ได้ด้วย ซึ่งจะช่วยให้คุณใช้ฟังก์ชันแบบแอสซิงค์ได้ตั้งแต่ IE8 ไปจนถึงเวอร์ชันล่าสุด โดยคุณจะต้องมีค่าที่กำหนดล่วงหน้า es2017 ของ Babel และค่าที่กำหนดล่วงหน้า es2015
เอาต์พุตจะไม่สวยมาก ดังนั้นโปรดระวังโค้ดที่ทำงานหนักเกินไป
ทำงานแบบไม่พร้อมกันทั้งหมด
เมื่อฟังก์ชันการทำงานแบบแอซิงค์พร้อมใช้งานในเบราว์เซอร์ทุกรุ่นแล้ว ให้ใช้ฟังก์ชันดังกล่าวกับฟังก์ชันที่แสดงผลพรอมิสทุกรายการ ไม่เพียงแต่จะทําให้โค้ดของคุณเป็นระเบียบมากขึ้น แต่ยังช่วยให้มั่นใจว่าฟังก์ชันจะแสดงผลพรอมต์เสมอ
เราตื่นเต้นมากเกี่ยวกับฟังก์ชันการทำงานแบบแอซิงค์เมื่อปี 2014 และดีใจที่ได้เห็นว่าฟังก์ชันเหล่านี้ใช้งานได้จริงในเบราว์เซอร์ ไชโย