使ç¨å¯è¯»æµ
ä½ä¸ºä¸ä¸ª JavaScript å¼åè ï¼ä»¥ç¼ç¨çæ¹å¼éåå°è¯»ååæä½ä»ç½ç»ä¸è·åçæ°æ®æ¯é常å®ç¨çï¼ä½æ¯ä½ è¦å¦ä½ä½¿ç¨ Stream API çå¯è¯»æµåè½å¢ï¼å¯ä»¥å¨è¿ç¯æç« çå°åºæ¬ä»ç»ã
夿³¨ï¼æ¬æè¦æ±ä½ å·²çè§£å¯è¯»æµç¸å ³ç¥è¯ï¼å¹¶äºè§£ç¸å ³çé«çº§æ¦å¿µï¼å¦æè¿ä¸äºè§£ï¼å»ºè®®ä½ å æ¥çæµçæ¦å¿µåç®ä»ä»¥åææ¡ Stream API æ¦å¿µç¶ååé è¯»æ¤æã
夿³¨ï¼å¦æä½ æ£å¨æ¥è¯¢å ³äºå¯åæµçä¿¡æ¯ï¼ä½ å¯ä»¥å°è¯é 读使ç¨å¯åæµã
寻æ¾ä¸äºç¤ºä¾
æä»¬å°å¨è¿ç¯æç« ä¸çå°åç§ç¤ºä¾ï¼å®ä»¬åèªæä»¬ç dom-examples/streams ä»åºãä½ å¯ä»¥å¨é£éåç°åç§æºä»£ç ï¼ä»¥åå ³èç示ä¾ã
ä½¿ç¨æµçæ¹å¼å¤ç Fetch
Fetch API å è®¸ä½ è·¨ç½ç»è·åèµæºï¼å®æä¾äºç°ä»£åç API 廿¿ä»£ XHRã宿ä¸ç³»åçä¼ç¹ï¼çæ£å¥½çæ¯ï¼æµè§å¨æè¿å¢å äºå° fetch ååºä½ä¸ºå¯è¯»æµä½¿ç¨çè½åã
Request.body å Response.body 屿§ä¹æ¯è¿æ ·ï¼å®ä»¬å°ä¸»ä½å
容æ´é²ä½ä¸ºä¸ä¸ªå¯è¯»æµç getterã
æ£å¦æä»¬çç®åæµå¼è¯»åç¤ºä¾æå±ç¤ºçï¼ä¹å¯ä»¥åè§å¨çº¿æ¼ç¤ºï¼ï¼æ´é²å®ä»
æ¯éè¦è®¿é®ååºç body 屿§ï¼
// Fetch the original image
fetch("./tortoise.png")
// Retrieve its body as ReadableStream
.then((response) => response.body);
è¿ä¸ºæä»¬æä¾äº ReadableStream 对象ã
éçä¸ä¸ª reader
ç°å¨æä»¬å·²ç»è·å尿们æµç主ä½ï¼è¯»åæµéè¦ç»å®éçä¸ä¸ª readerãä½¿ç¨ ReadableStream.getReader() æ¹æ³å®æï¼
// Fetch the original image
fetch("./tortoise.png")
// Retrieve its body as ReadableStream
.then((response) => response.body)
.then((body) => {
const reader = body.getReader();
// â¦
});
è°ç¨è¿ä¸ªæ¹æ³å建ä¸ä¸ª reader å¹¶ä¸ç¨å®éå®è¿ä¸ªæµââç´å°éæ¾è¿ä¸ª readerï¼å³éè¿è°ç¨ ReadableStreamDefaultReader.releaseLock()ï¼ï¼æ²¡æå
¶ä» reader è½è¯»è¿ä¸ªæµã
å¦è¯·æ³¨æï¼å
åç示ä¾å¯ä»¥åå°ä¸æ¥ï¼ç±äº response.body æ¯åæ¥çï¼æä»¥ä¸éè¦ promiseï¼
// Fetch the original image
fetch("./tortoise.png")
// Retrieve its body as ReadableStream
.then((response) => {
const reader = response.body.getReader();
// â¦
});
è¯»åæµ
ç°å¨ä½ å·²ç»éçäºä½ ç readerï¼ä½ å¯ä»¥ä½¿ç¨ ReadableStreamDefaultReader.read() æ¹æ³ä»æµä¸è¯»åæ°æ®ååãä½ ä»æµä¸è¯»åºåååï¼å¯ä»¥åä½ åæ¬¢çäºãä¾å¦ï¼æä»¬çç®åæµå¼è¯»å示ä¾å°ååéå
¥æ°çèªå®ä¹ ReadableStream ä¸ï¼æä»¬å°å¨ä¸ä¸èåç°æ´å¤ä¿¡æ¯ï¼ï¼ç¶åä»ä¸å建ä¸ä¸ªæ°çååºï¼å°å®ä½ä¸º Blob 使ç¨ï¼ç¶åéè¿ URL.createObjectURL() ä»è¯¥ blob å建ä¸ä¸ªå¯¹è±¡ URLï¼å¹¶å°å
¶æ¾ç¤ºå¨å±å¹ä¸ç <img> å
ç´ ä¸ï¼ææå°åå»ºäºæä»¬æåè·åçå¾åç坿¬ã
// Fetch the original image
fetch("./tortoise.png")
// Retrieve its body as ReadableStream
.then((response) => {
const reader = response.body.getReader();
return new ReadableStream({
start(controller) {
return pump();
function pump() {
return reader.read().then(({ done, value }) => {
// When no more data needs to be consumed, close the stream
if (done) {
controller.close();
return;
}
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
return pump();
});
}
},
});
})
// Create a new response out of the stream
.then((stream) => new Response(stream))
// Create an object URL for the response
.then((response) => response.blob())
.then((blob) => URL.createObjectURL(blob))
// Update image
.then((url) => console.log((image.src = url)))
.catch((err) => console.error(err));
让æä»¬è¯¦ç»ççå¦ä½ä½¿ç¨ read()ãå¨ pump() 彿°ä¸ï¼æä»¬é¦å
è°ç¨ read()ï¼å
¶è¿åä¸ä¸ªå
å«å¯¹è±¡ç promiseââè¿éææä»¬è¦è¯»å»çç»æï¼å
¶å½¢å¼ä¸º { done, value }ï¼
reader.read().then(({ done, value }) => {
/* ⦠*/
});
è¿ä¸ªç»æå¯è½æ¯ä¸ç§ä¸åçç±»åä¹ä¸ï¼
- 妿æååå¯ç¨ï¼å promise å°ä½¿ç¨
{ value: theChunk, done: false }å½¢å¼ç对象æ¥å ç°ã - 妿æµå·²ç»å
³éï¼å promise å°ä½¿ç¨
{ value: undefined, done: true }å½¢å¼ç对象æ¥å ç°ã - 妿æµåçé误ï¼å promise å°å ç¸å ³é误被æç»ã
å
¶æ¬¡ï¼æä»¬æ£æ¥ done æ¯å¦ä¸º trueã妿æ¯ï¼åæ²¡ææ´å¤çååè¦è¯»åï¼value ç弿¯ undefinedï¼ï¼æä»¥æä»¬éåºè¿ä¸ªå½æ°å¹¶ä¸ä½¿ç¨ ReadableStreamDefaultController.close() å
³éèªå®ä¹çæµï¼
if (done) {
controller.close();
return;
}
夿³¨ï¼close() æ¯æ°èªå®ä¹æµçä¸é¨åï¼è䏿¯æä»¬å¨è¿ä¸ªè®¨è®ºçä¸é¨åãæä»¬å°å¨ä¸ä¸èéè¿°æ´å¤å
³äºèªå®ä¹æµçå
容ã
妿 done 为 trueï¼æä»¬å¤çå·²ç»è¯»åçæ°ååï¼å
å«å¨ç»æå¯¹è±¡ç value 屿§ï¼ï¼ç¶å忬¡è°ç¨ pump() 彿°å»è¯»åä¸ä¸ä¸ªååã
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
return pump();
è¿æ¯å½ä½ å¨ä½¿ç¨æµç reader æ¶ï¼å°çè§çæ åçæ¨¡å¼ï¼
- ç¼åä¸ä¸ªä»æµç读åå¼å§ç彿°ã
- 妿æµä¸æ²¡ææ´å¤çååè¦è¯»åï¼ä½ éè¦éåºè¿ä¸ªå½æ°ã
- 妿æµä¸ææ´å¤çååè¦è¯»åï¼ä½ å¯ä»¥å¤çå½åçåååï¼å次è¿è¡è¯¥å½æ°ã
- ä½ ç»§ç»é¾æ¥
pipe彿°ï¼ç´å°æ²¡ææ´å¤æµè¦è¯»åï¼å¨è¿ç§æ åµä¸ï¼è¯·éµå¾ªæ¥éª¤ 2ã
夿³¨ï¼è¯¥å½æ°çèµ·æ¥å pump() è°ç¨èªå·±å¹¶ä¸å¯¼è´ä¸ä¸ªæ½å¨ç深度éå½ãç¶èï¼å 为 pump æ¯å¼æ¥ç并䏿¯æ¬¡è°ç¨ pump() 齿¯å¨ promise å¤çç¨åºçæ«å°¾ï¼äºå®ä¸ï¼å®ç±»ä¼¼äº promise å¤çç¨åºçé¾å¼ç»æã
åå»ºä½ èªå®ä¹çå¯è¯»æµ
æä»¬å¨æ¬æä¸ä¸ç´å¨ç ç©¶ç®åæµå¼è¯»å示ä¾ï¼å
æ¬ç¬¬äºé¨åââ䏿¦æä»¬ä» fetch 主ä½ä¸ä»¥ååçå½¢å¼è¯»åå¾çï¼æä»¬å°±å¯ä»¥å°å®ä»¬æå
¥å¦ä¸ä¸ªæä»¬èªå®ä¹çæµä¸ãæä»¬è¯¥å¦ä½å建 ReadableStream() æé 彿°ï¼
ReadableStream() æé 彿°
彿µè§å¨ä¸ºä½ æä¾æµæ¶ï¼å¯ä»¥å¾å®¹æç读åï¼æ£å¦ Fetch çæ
åµä¸æ ·ï¼ä½æ¯ææ¶åä½ éè¦å建ä¸ä¸ªèªå®ä¹æµå¹¶ä¸ç¨ä½ èªå·±çååå¡«å
å®ãReadableStream() æé 彿°å
è®¸ä½ éè¿æåçèµ·æ¥å¾å¤æçè¯æ³å建å®ï¼ä½æ¯è¿ç¡®å®ä¸æ¯æç³ç³çã
éç¨çè¯æ³æ¡æ¶åè¿æ ·ï¼
const stream = new ReadableStream(
{
start(controller) {},
pull(controller) {},
cancel() {},
type,
autoAllocateChunkSize,
},
{
highWaterMark: 3,
size: () => 1,
},
);
æé 彿°éè¦ä¸¤ä¸ªå¯¹è±¡ä½ä¸ºåæ°ã第ä¸ä¸ªå¯¹è±¡æ¶å¿ éçï¼å¹¶å¨ JavaScript ä¸å建ä¸ä¸ªæ£å¨è¯»åæ°æ®çåºå±æºæ¨¡åã第äºä¸ªå¯¹è±¡æ¯å¯éçï¼å¹¶ä¸å è®¸ä½ å»æå®ä¸ä¸ªèªå®ä¹çéåçç¥ç¨äºèªå·±çæµãä½ å°å¾å°è¿ä¹åï¼æä»¥æä»¬ç°å¨åªè¦ä¸æ³¨äºç¬¬ä¸ä¸ªã
第ä¸ä¸ªå¯¹è±¡å å«çäºä¸ªæåï¼ä» æç¬¬ä¸ä¸ªæ¯å¿ è¦çï¼
start(controller)ââä¸ä¸ªå¨ReadableStreamæå»ºåï¼ç«å³è¢«è°ç¨ä¸æ¬¡çæ¹æ³ãå¨è¿ä¸ªæ¹æ³ä¸ï¼ä½ åºè¯¥å å«è®¾ç½®æµåè½ç代ç ï¼ä¾å¦å¼å§çææ°æ®æè ä»¥å ¶ä»çæ¹å¼è®¿é®èµæºæ¶ãpull(controller)ââä¸ä¸ªæ¹æ³ï¼å½è¢«å 嫿¶ï¼å®ä¼è¢«éå¤çè°ç¨ç´å°å¡«æ»¡æµçå ç½®éåãå½æå ¥æ´å¤çååæ¶ï¼è¿å¯ä»¥ç¨äºæ§å¶æµãcancel()ââä¸ä¸ªæ¹æ³ï¼å½è¢«å 嫿¶ï¼å¦æåºç¨ååºæµå°è¢«åæ¶çä¿¡å·ï¼å®å°è¢«è°ç¨ï¼ä¾å¦ï¼è°ç¨ReadableStream.cancel()ï¼ãå 容åºè¯¥éåä»»ä½å¿ è¦çæªæ½éæ¾å¯¹æµæºç访é®ãtypeåautoAllocateChunkSizeââå½å®ä»¬è¢«å 嫿¶ï¼ä¼è¢«ç¨æ¥è¡¨ç¤ºæµå°æ¯ä¸ä¸ªåèæµãåèæµå°å¨æªæ¥çæç¨ä¸åç¬æ¶µçï¼å 为å®ä»¬å¨ç®çåç¨ä¾ä¸ä¸å¸¸è§çï¼é»è®¤çï¼æµæäºä¸åãå®ä»¬ä¹æªå¨ä»»ä½å°æ¹å®æ½ã
忬¡çæä»¬çç®å示ä¾ä»£ç ï¼ä½ å¯ä»¥çè§æä»¬çæé 彿° ReadableStream() ä»
å
å«ä¸ä¸ªåç¬çæ¹æ³ââstart()ï¼å®ç¨äºè¯»åæä»¬ fetch æµä¸çææçæ°æ®ã
// Fetch the original image
fetch("./tortoise.png")
// Retrieve its body as ReadableStream
.then((response) => {
const reader = response.body.getReader();
return new ReadableStream({
start(controller) {
return pump();
function pump() {
return reader.read().then(({ done, value }) => {
// When no more data needs to be consumed, close the stream
if (done) {
controller.close();
return;
}
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
return pump();
});
}
},
});
});
ReadableStream controller
ä½ å°æ³¨æå°ä¼ éç» ReadableStream æé 彿°ç start() å pull() æ¹æ³æå®äº controller åæ°ââè¿äºæ¯ ReadableStreamDefaultController ç±»çå®ä¾ï¼å®å¯ä»¥ç¨äºæ§å¶ä½ çæµã
卿们ç示ä¾ä¸ï¼å½è¯»å fetch 主ä½åï¼ä½¿ç¨ controller ç enqueue() æ¹æ³å»å° value æå
¥èªå®ä¹çæµä¸ã
å¦å¤ï¼å½æä»¬å®æè¯»å fetch 主ä½ï¼æä»¬ä½¿ç¨ controller ç close() æ¹æ³å»å
³éèªå®ä¹æµââä»»ä½å
åæå
¥çååä»ç¶å¯ä»¥ä»ä¸è¯»åï¼ä½æ¯ä¸ä¼æå
¥æ´å¤çååï¼å¹¶ä¸å½è¯»åç»ææ¶æµè¢«å
³éã
读åèªå®ä¹æµ
卿们çç®åæµå¼è¯»å示ä¾ä¸ï¼æä»¬å°å
¶ä¼ éå° Response æé 彿°ä¸æ¥æ¶è´¹èªå®ä¹çå¯è¯»æµï¼ç¶åæä»¬å°å®ä½ä¸º blob() æ¥ä½¿ç¨ã
readableStream
.then((stream) => new Response(stream))
.then((response) => response.blob())
.then((blob) => URL.createObjectURL(blob))
.then((url) => console.log((image.src = url)))
.catch((err) => console.error(err));
使¯ä¸ä¸ªèªå®ä¹æµä»ç¶æ¯ ReadableStream å®ä¾ï¼æå³çä½ å¯ä»¥ç»å®éçä¸ä¸ª readerãä¾å¦ï¼ççæä»¬çç®åéæºæµç¤ºä¾ï¼ä¹å¯ä»¥åè§å¨çº¿æ¼ç¤ºï¼ï¼å
¶å建äºä¸ä¸ªèªå®ä¹çæµï¼æå
¥äºä¸äºéæºçå符串ï¼ç¶åå¨æä¸ Stop string generation çæé®åï¼åæ¬¡ä»æµä¸è¯»åæ°æ®ã
夿³¨ï¼ä¸ºäºä½¿ç¨ FetchEvent.respondWith() æ¶è´¹æµï¼æå
¥çæµå
容çç±»åå¿
é¡»æ¯ Uint8Arrayï¼ä¾å¦ä½¿ç¨ TextEncoder è¿è¡ç¼ç ã
èªå®ä¹æµçæé 彿°æä¸ä¸ª start() æ¹æ³ï¼è¯¥æ¹æ³ä½¿ç¨ setInterval() 廿宿¯ç§çæä¸ä¸ªéæºçå符串ãç¶åä½¿ç¨ ReadableStreamDefaultController.enqueue() å°å®æå
¥æµã彿䏿é®ï¼åæ¶ intervalï¼å¹¶è°ç¨å为 readStream() 彿°åæ¬¡å°æ°æ®ä»æµä¸è¯»å忥ãç±äºæä»¬ä¸ç´åæ¢æå
¥ååï¼æä»¥æä»¬ä¹è¦å
³éæµã
const stream = new ReadableStream({
start(controller) {
interval = setInterval(() => {
const string = randomChars();
// Add the string to the stream
controller.enqueue(string);
// show it on the screen
const listItem = document.createElement("li");
listItem.textContent = string;
list1.appendChild(listItem);
}, 1000);
button.addEventListener("click", () => {
clearInterval(interval);
readStream();
controller.close();
});
},
pull(controller) {
// We don't really need a pull in this example
},
cancel() {
// This is called if the reader cancels,
// so we should stop generating strings
clearInterval(interval);
},
});
å¨ readStream() 彿°ä¸ï¼æä»¬ä½¿ç¨ ReadableStream.getReader() å° reader éå®å°è¯¥æµï¼ç¶åéµå¾ªæä»¬ä¹åçå°çç¸åçæ¨¡å¼ââä½¿ç¨ reader è¯»åæ¯ä¸ªååï¼å¨å次è¿è¡ read() æ¹æ³ä¹åï¼æ£æ¥ done æ¯å¦ä¸º trueï¼å¦ææ¯ trueï¼å¤çç»æï¼å¦ææ¯ falseï¼è¯»åä¸ä¸ä¸ªååå¹¶ä¸å¤çå®ã
function readStream() {
const reader = stream.getReader();
let charsReceived = 0;
let result = "";
// read() returns a promise that resolves
// when a value has been received
reader.read().then(function processText({ done, value }) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (done) {
console.log("Stream complete");
para.textContent = result;
return;
}
charsReceived += value.length;
const chunk = value;
const listItem = document.createElement("li");
listItem.textContent = `Read ${charsReceived} characters so far. Current chunk = ${chunk}`;
list2.appendChild(listItem);
result += chunk;
// Read some more, and call this function again
return reader.read().then(processText);
});
}
å ³éå¹¶åæ¶æµ
æä»¬å·²ç»å±ç¤ºäºä½¿ç¨ ReadableStreamDefaultController.close() å»å
³é reader ç示ä¾ãæ£å¦æä»¬ä¹å说ç飿 ·ï¼ä»»ææå
¥éçååå°ä»ç¶è¢«è¯»åï¼ä½æ¯å 为å®è¢«å
³éäºï¼ä¸ä¼åææ´å¤çååå
¥éã
å¦æä½ æ³è¦å®å
¨çæè±æµå¹¶ä¸ä¸¢å¼ææå
¥éçååï¼ä½ åºè¯¥ä½¿ç¨ ReadableStream.cancel() æ ReadableStreamDefaultReader.cancel()ã
æ·è´æµ
ææ¶åä½ å¯è½æ³è¦åæ¶è¯»å两次æµã该è¿ç¨ç±è°ç¨ ReadableStream.tee() å®ç°ââå®è¿åä¸ä¸ªæ°ç»ï¼å
å«å¯¹åå§å¯è¯»æµç两个ç¸åç坿¬å¯è¯»æµï¼ç¶åå¯ä»¥ç¬ç«ç使ç¨ä¸åç reader 读åã
举ä¾èè¨ï¼ä½ å¨ ServiceWorker ä¸å¯è½ä¼ç¨å°è¯¥æ¹æ³ï¼å½ä½ 仿å¡å¨ fetch èµæºï¼å¾å°ä¸ä¸ªååºçå¯è¯»æµï¼ä½ å¯è½ä¼æ³æè¿ä¸ªæµæåæä¸¤ä¸ªï¼ä¸ä¸ªæµå ¥å°æµè§å¨ï¼å¦ä¸ä¸ªæµå ¥å° ServiceWorker çç¼åãç±äºååºç䏻使 æ³è¢«æ¶è´¹ä¸¤æ¬¡ï¼ä»¥åå¯è¯»æµæ æ³è¢«ä¸¤ä¸ª reader åæ¶è¯»åï¼ä½ ä¼éè¦ä¸¤ä¸ªå¯è¯»æµå¯æ¬æ¥å®ç°éæ±ã
æä»¬æä¾äºä¸ä¸ªç¤ºä¾ï¼å¨æä»¬çç®åæ·è´ç¤ºä¾ï¼ä¹å¯ä»¥åè§å¨çº¿æ¼ç¤ºï¼ãè¿ä¸ªç¤ºä¾ä¸æä»¬çç®åéæºæµç¤ºä¾ç工使¹å¼å¤§è´ç¸åï¼åªæ¯å½æé®æä¸åæ¢çäº§éæºå符串æ¶ï¼å°éåèªå®ä¹æµå¹¶æ·è´æµï¼å¹¶ä¸è¯»åè¿ä¸¤ä¸ªçæçæµï¼
function teeStream() {
const teedOff = stream.tee();
readStream(teedOff[0], list2);
readStream(teedOff[1], list3);
}
é¾å¼ç®¡éä¼ è¾
æµçå¦ä¸ç¹å¾æ¯éè¿ç®¡éçæ¹å¼ä»ä¸ä¸ªæµè¾åºå°å¦ä¸ä¸ªï¼ç§°ä¸ºé¾å¼ç®¡éä¼ è¾ï¼ãè¿ä¼è°ç¨ä¸¤ä¸ªæ¹æ³ââReadableStream.pipeThrough()ï¼å®å°å¯è¯»æµç®¡éè¾åºè³æ¥æä¸å¯¹ writer/reader çæµä¸ï¼å¹¶å°ä¸ç§æ°æ®è½¬æ¢æå¦ä¸ç§ï¼ReadableStream.pipeTo() å°å¯è¯»æµç®¡éä¼ è¾è³ä½ä¸ºé¾å¼ç®¡éä¼ è¾ç»ç¹ç writerã
æä»¬æä¸ä¸ªç®åç示ä¾ï¼å«åè§£å PNG ååï¼ä¹å¯ä»¥åè§å¨çº¿æ¼ç¤ºï¼ãæ¤ç¤ºä¾å°å¾åä½ä¸ºæµæ¥è·åï¼ç¶åå°å®ä¼ è¾å°èªå®ä¹ç PNG è½¬æ¢æµï¼è¯¥æµå°ä»äºè¿å¶æ°æ®æµä¸æ£ç´¢ PNG ååã
// Fetch the original image
fetch("png-logo.png")
// Retrieve its body as ReadableStream
.then((response) => response.body)
// Create a gray-scaled PNG stream out of the original
.then((rs) => logReadableStream("Fetch Response Stream", rs))
.then((body) => body.pipeThrough(new PNGTransformStream()))
.then((rs) => logReadableStream("PNG Chunk Stream", rs));
æä»¬ä»ç¶æ²¡æä½¿ç¨ TransformStream çä¾åã
æ»ç»
è¿è§£éäºâé»è®¤âå¯è¯»æµçç¥è¯ã
å ³äºå¦ä½ä½¿ç¨å¯è¯»åèæµçä¿¡æ¯ï¼åè§ä½¿ç¨å¯è¯»åèæµï¼å ·æåºå±åèæºçæµï¼å¯ä»¥åæ¶è´¹è æ§è¡é«æçé¶å¤å¶ä¼ è¾ï¼ç»è¿æµçå é¨éåã