ฟังก์ชันอะซิงโครนัสช่วยให้คุณเขียนโค้ดตามสัญญาได้เสมือนกับว่าเป็นแบบซิงโครนัส
ฟังก์ชัน Async จะเปิดใช้โดยค่าเริ่มต้นใน Chrome, Edge, Firefox และ Safari ซึ่งถือว่ายอดเยี่ยมมาก ซึ่งช่วยให้คุณเขียนโค้ดตามสัญญาได้เสมือนว่า เป็นแบบซิงโครนัส แต่ไม่บล็อกเทรดหลัก วิธีนี้จะช่วยให้โค้ดแบบอะซิงโครนัสของคุณ "ฉลาด" และอ่านง่ายขึ้น
ฟังก์ชันอะซิงโครนัสมีการทำงานดังนี้
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
หากใช้คีย์เวิร์ด async
ก่อนคําจํากัดความของฟังก์ชัน คุณจะใช้ await
ภายในฟังก์ชันได้ เมื่อคุณawait
สัญญา ฟังก์ชันจะหยุดชั่วคราวในแบบที่ไม่บล็อกจนกว่าสัญญาจะตกลง หากสัญญาเป็นไปตามสิ่งที่สัญญาไว้
คุณจะได้รับคุณค่ากลับคืนมา หากคำสัญญาปฏิเสธ ระบบจะส่งค่าที่ถูกปฏิเสธ
การสนับสนุนเบราว์เซอร์
ตัวอย่าง: การบันทึกการดึงข้อมูล
สมมติว่าคุณต้องการดึงข้อมูล URL และบันทึกคำตอบเป็นข้อความ วิดีโอจะมีลักษณะดังนี้ โดยใช้คำสัญญา
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);
}
}
จำนวนบรรทัดเท่ากัน แต่ Callback ทั้งหมดจะหายไป วิธีนี้จะช่วยให้อ่านได้ง่ายขึ้น โดยเฉพาะสำหรับผู้ที่ไม่ค่อยคุ้นเคยกับสัญญา
ค่าการแสดงผลแบบไม่พร้อมกัน
ฟังก์ชันอะซิงโครนัสจะให้ผลลัพธ์ทุกครั้ง ไม่ว่าคุณจะใช้ await
หรือไม่ก็ตาม Promise จะแปลงผลลัพธ์ด้วยไม่ว่าฟังก์ชันอะซิงโครนัสจะส่งกลับมา หรือปฏิเสธเมื่อมีฟังก์ชันอะซิงโครนัสก็ตาม ดังนั้นด้วย:
// 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 "ผู้นำทางแห่งคำสัญญา" อาร์คิบัลด์ ดูว่าฉันเรียก 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
ด้วยลูปสำหรับวงที่ล้อมรอบด้วย ทำให้ราบรื่นยิ่งขึ้น
ไวยากรณ์ฟังก์ชันแบบไม่พร้อมกันอื่นๆ
ฉันแสดง 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 มิลลิวินาทีในการดำเนินการ เนื่องจากการรอทั้งสองแบบเกิดขึ้นพร้อมกัน มาดูตัวอย่างที่ใช้ได้จริงกัน
ตัวอย่าง: การแสดงเอาต์พุตตามลำดับ
สมมติว่าคุณต้องการดึงข้อมูลชุด 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
เพื่อผูกการเรียงลำดับคำสัญญา ฉันฉลาดมากเลย แต่วิธีนี้เป็นการเขียนโค้ดที่ฉลาดมากสักหน่อย คุณจะมีประสิทธิภาพมากกว่าหากไม่มี
อย่างไรก็ตาม เมื่อแปลงฟังก์ชันข้างต้นเป็นฟังก์ชันอะซิงโครนัส คุณอาจอยากเรียงลำดับมากเกินไป เช่น
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); } }
วิธีแก้ปัญหาการสนับสนุนเบราว์เซอร์: โปรแกรมสร้าง
หากกำหนดเป้าหมายเป็นเบราว์เซอร์ที่รองรับโปรแกรมสร้าง (ซึ่งรวมถึงเบราว์เซอร์หลักเวอร์ชันล่าสุดทั้งหมด) คุณจะจัดเรียงฟังก์ชันอะซิงโครนัส Polyfill ได้
Babel จะจัดการให้คุณ นี่คือตัวอย่างจากการตอบกลับของ Babel
- ดูว่าโค้ดที่เปลี่ยนรูปแบบมีความคล้ายคลึงกันมากเพียงใด การเปลี่ยนรูปแบบนี้เป็นส่วนหนึ่งของค่าที่กำหนดล่วงหน้า es2017 ของ Babel
ผมแนะนำให้ใช้วิธีการสลับรูปแบบ เพราะคุณจะปิดเมื่อเบราว์เซอร์เป้าหมายรองรับฟังก์ชันอะซิงโครนัส แต่ถ้าคุณไม่ต้องการใช้ตัวแปลงจริงๆ ก็ใช้โพลีฟิลล์ของ Babel เพื่อนำมาใช้เอง แทนที่จะเป็น:
async function slowEcho(val) {
await wait(1000);
return val;
}
...คุณจะต้องใส่ polyfill และเขียนว่า
const slowEcho = createAsyncFunction(function* (val) {
yield wait(1000);
return val;
});
โปรดทราบว่าคุณต้องส่งโปรแกรมสร้าง (function*
) ไปยัง createAsyncFunction
และใช้ yield
แทน await
นอกจากนี้ ยังทำงานเหมือนเดิม
วิธีแก้ปัญหาเบื้องต้น: เครื่องมือสร้างใหม่
หากคุณกำหนดเป้าหมายเป็นเบราว์เซอร์รุ่นเก่า Babel ก็สามารถแปลงเครื่องมือสร้างได้ ซึ่งช่วยให้คุณใช้ฟังก์ชันแบบอะซิงโครนัสได้จนถึง IE8 หากต้องการดำเนินการนี้ คุณต้องมีค่าที่กำหนดล่วงหน้า es2017 ของ Babel และค่าที่กำหนดล่วงหน้า es2015
เอาต์พุตค่อนข้างไม่สวยนัก คุณจึงต้องระวังโค้ดที่มากเกินไป
ไม่ซิงค์ทุกเรื่องเลย!
เมื่อฟังก์ชันอะซิงโครนัสเข้ามาอยู่ในทุกเบราว์เซอร์แล้ว ให้ใช้ฟังก์ชันเหล่านั้นในทุกฟังก์ชันการส่งคืนคำสัญญา! นอกจากจะทำให้โค้ดดูเป็นระเบียบขึ้นแล้ว ยังทำให้ฟังก์ชันทำงานได้อย่างเต็มประสิทธิภาพเสมอด้วย
เมื่อปี 2014 ผมตื่นเต้นมากกับฟังก์ชันอะซิงโครนัส และผมรู้สึกดีที่ได้เห็นว่าฟังก์ชันอะซิงโครนัสพร้อมใช้งานจริงในเบราว์เซอร์ ไชโย!