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