var ts = function(){ dt = new Date(); year = dt.getFullYear(); month = (dt.getMonth()+1).toString().padStart(2, '0'); day = dt.getDate().toString().padStart(2, '0'); hour = dt.getHours().toString().padStart(2, '0'); min = dt.getMinutes().toString().padStart(2, '0'); sec = dt.getSeconds().toString().padStart(2, '0'); ms = dt.getMilliseconds(); str = year + "-" + month + "-" + day + " " + hour + ":" + min + ":" + sec + "." + ms return str } var url = "<big data URL>" fetch(url) .then((response) => response.body.getReader()) .then((reader) => { var charsReceived = 0; function processText({ done, value }) { if (done) { console.log("Stream complete"); return; } charsReceived += value.length; console.log( ts() + " " + value.length + " bytes received. total size=" + charsReceived + " bytes"); return reader.read().then(processText); } return reader.read().then(processText); });
実行結果
2023-09-09 11:34:19.427 16384 bytes received. total size=16384 bytes 2023-09-09 11:34:19.519 32768 bytes received. total size=49152 bytes 2023-09-09 11:34:19.522 32274 bytes received. total size=81426 bytes 2023-09-09 11:34:19.614 65536 bytes received. total size=146962 bytes 2023-09-09 11:34:19.614 32491 bytes received. total size=179453 bytes 2023-09-09 11:34:19.618 16098 bytes received. total size=195551 bytes 2023-09-09 11:34:19.708 65536 bytes received. total size=261087 bytes 2023-09-09 11:34:19.709 36951 bytes received. total size=298038 bytes Stream complete
とりあえず動いた。
参考:
- Response.body - Web API | MDN
- ReadableStream: getReader() method - Web APIs | MDN
- Streaming requests with the fetch API - Chrome Developers
- JavaScriptのStreams APIで細切れのデータを読み書きする
疑問点
- そもそもPythonのdatetimeっぽい時刻表示にするためにあんな泥臭いことやらないといけない?strftimeとかないの?
- printf()あるいはformat()みたいな文字列フォーマットはないの?
- reader.read() が完了する(fulfilledになる)タイミングはどこで決まってる?変更できるのかな?
- 再起じゃなくてループにはできないのかな?awaitしないとwhileループで書けない?
javascript慣れてないからお作法というか文化的なものが分からんなあ。。
追記
async版。こっちの方が分かりやすいな。Promise分かんなすぎ。
(async () => { var charsReceived = 0; const response = await fetch(url); const reader = response.body.getReader(); while (true) { const {value, done} = await reader.read(); if (done) break; charsReceived += value.length; console.log( ts() + " " + value.length + " bytes received. total size=" + charsReceived + " bytes"); } console.log('Stream complete'); })()
あとちなみに、valueはバイト列(Uint8Array)として渡されるので、テキストとして扱う場合はTextDecoder()でデコードするか
TextDecoderStream()をTransformStreamとして指定すればよいらしい。
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
わぁーおしゃれ!
追記2
最初は一番下のreturn reader.read().then(processText)の中にprocessTextの定義を埋め込んでたんだけど、そうすると途中でPromiseが切れる(という言い方が適切なのかは分からない)というか
.thenで繋いだときに読み込みが終わる前に実行されてしまうので分けて定義しないといけないみたい。