Streaming Instantiation และ Fetch
ในแอปพลิเคชันจริง คุณจะไม่มี WAT source ในขณะ runtime — แต่จะ fetch ไฟล์ binary .wasm ที่คอมไพล์แล้วจาก server แทน API ที่แนะนำสำหรับวิธีนี้คือ WebAssembly.instantiateStreaming() ซึ่งเริ่มคอมไพล์ bytes ตั้งแต่เริ่มรับข้อมูลผ่านเครือข่าย แทนที่จะรอให้ดาวน์โหลดเสร็จสมบูรณ์ก่อน
instantiateStreaming เทียบกับ instantiate
หัวข้อที่มีชื่อว่า “instantiateStreaming เทียบกับ instantiate”WebAssembly.instantiate ต้องการให้ byte buffer ทั้งหมดอยู่ในหน่วยความจำก่อนจึงจะเริ่มคอมไพล์ได้ สำหรับ module ขนาดใหญ่ หมายความว่า browser จะนั่งรอระหว่างการดาวน์โหลด แล้วค่อยจ่ายต้นทุนการคอมไพล์ทั้งหมดในครั้งเดียว
WebAssembly.instantiateStreaming ส่ง bytes ให้คอมไพเลอร์ขณะที่กำลัง stream มาจากเครือข่าย การคอมไพล์และการดาวน์โหลดทับซ้อนกัน ทำให้ module พร้อมใช้งานได้เร็วขึ้น — ซึ่งเห็นได้ชัดโดยเฉพาะเมื่อการเชื่อมต่อช้าหรือสำหรับ module ที่มีขนาดหลายร้อยกิโลไบต์ขึ้นไป
// instantiate — waits for full download before compilingconst response = await fetch('/module.wasm');const bytes = await response.arrayBuffer();const { instance } = await WebAssembly.instantiate(bytes, {});
// instantiateStreaming — compiles while bytes arriveconst { instance } = await WebAssembly.instantiateStreaming( fetch('/module.wasm'), {});โปรดสังเกตว่า instantiateStreaming รับ Promise<Response> ที่ fetch คืนมาโดยตรง — ไม่จำเป็นต้อง await fetch ก่อน
ข้อกำหนดด้าน MIME Type
หัวข้อที่มีชื่อว่า “ข้อกำหนดด้าน MIME Type”WebAssembly.instantiateStreaming จะตรวจสอบ header Content-Type ของ response ก่อนเริ่ม stream bytes ไปยังคอมไพเลอร์ Server ต้อง ให้บริการไฟล์ .wasm ด้วย Content-Type: application/wasm หากส่ง type อื่น เช่น application/octet-stream API จะ reject ทันทีด้วย TypeError ก่อนที่จะคอมไพล์ bytes ใดๆ
เมื่อไม่สามารถควบคุมการตั้งค่า MIME type ของ server ได้ ให้ใช้รูปแบบสองขั้นตอนแทน:
async function loadWasm(url, imports) { try { return await WebAssembly.instantiateStreaming(fetch(url), imports); } catch (e) { if (e instanceof TypeError) { // MIME type wrong — fall back to arrayBuffer const response = await fetch(url); const bytes = await response.arrayBuffer(); return WebAssembly.instantiate(bytes, imports); } throw e; }}การส่ง Imports
หัวข้อที่มีชื่อว่า “การส่ง Imports”imports object ทำงานเหมือนกับที่ใช้ใน WebAssembly.instantiate ทุกประการ — เป็น argument ที่สอง และโครงสร้างต้องตรงกับ import section ของ module อย่างแม่นยำ
const result = await WebAssembly.instantiateStreaming( fetch('/module.wasm'), { env: { log: n => console.log(n) } });การ Cache Compiled Modules
หัวข้อที่มีชื่อว่า “การ Cache Compiled Modules”WebAssembly.Module ที่อยู่ใน result.module คือ compiled artifact ที่สามารถ serialize ได้ คุณสามารถเก็บไว้ใน IndexedDB เพื่อให้การเข้าชมครั้งถัดไปข้ามขั้นตอนการคอมไพล์ทั้งหมดและข้ามไปยัง instantiation ได้เลย:
const { module, instance } = await WebAssembly.instantiateStreaming( fetch('/module.wasm'), {});// Store `module` in IndexedDB for next visit// Later: const instance = await WebAssembly.instantiate(cachedModule, {});ลองรันดู
หัวข้อที่มีชื่อว่า “ลองรันดู”Sandbox นี้ไม่มี server ให้ fetch ดังนั้นจึงไม่สามารถใช้ fetch() ได้ ตัวรันด้านล่างใช้ compileWat() เพื่อสร้าง compiled bytes แล้วเรียก WebAssembly.instantiate โดยตรง — ที่เป็นเส้นทางเดียวกับที่ streaming fallback จะใช้หลังจากได้รับ bytes แล้ว
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
instantiateStreaming | เริ่มคอมไพล์ทันทีที่ bytes แรกมาถึง ลด time-to-interactive ได้ชัดเจนสำหรับ module ขนาดใหญ่ | ต้องพึ่ง server ส่ง header Content-Type: application/wasm ให้ถูกต้อง มิเช่นนั้น reject ด้วย TypeError |
fallback ด้วย arrayBuffer() + instantiate | ทำงานได้แน่นอนแม้ควบคุม MIME type ของ server ไม่ได้ | เสียเวลารอดาวน์โหลดครบไฟล์ก่อนเริ่มคอมไพล์ ทำให้ startup ช้าลง |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เรียก
fetch()แล้วawait response.arrayBuffer()ก่อนเสมอโดยไม่ลองinstantiateStreamingก่อน ทั้งที่ควรใช้ streaming เป็นค่าเริ่มต้นแล้ว fallback เฉพาะตอนจับTypeError - ลืมตั้งค่า server (nginx, Express ฯลฯ) ให้ map นามสกุล
.wasmเป็นapplication/wasmทำให้instantiateStreamingreject ทุกครั้งโดยไม่รู้สาเหตุ - ส่งข้อมูลก้อนใหญ่กลับไปมาผ่าน exports/imports ทีละ call หลัง instantiate สำเร็จ แทนที่จะใช้ shared linear memory เขียน-อ่านครั้งเดียว ทำให้เสีย throughput ที่ streaming ช่วยประหยัดมาไปเปล่าๆ
💡 ตัวอย่างจากของจริง
Squoosh ใช้
instantiateStreamingโหลด codec แต่ละตัวเพื่อให้ startup เร็วที่สุด ส่วน AutoCAD Web ก็อาศัย streaming instantiation ในการโหลด engine ขนาดใหญ่ ควบคู่กับ interop ที่มีประสิทธิภาพระหว่าง JS กับ Wasm เพื่อให้ผู้ใช้โต้ตอบกับโมเดล CAD ได้แบบ real-time