Uint8Array 타입은 나중에 자세히 다룰 예정이다. 간단하게만 짚고 넘어가면 ArrayBuffer 객체의 일종으로 원시(raw) 이진 데이터에 액세스하기 위한 메커니즘을 제공해 배열 최적화를 수행하는데 기여한다.
await reader.read() 의 호출 결과는 두개의 프로퍼티를 가지고 있는 객체를 반환하는데, 각각의 프로퍼티는 위 코드에서 쓰이고 있는 것과 같다.
done : 읽기가 끝난 경우 true, 아니면 false
value : 바이트를 나타내는 형식화 배열(Uint8Array)
ReadableStream 객체는 Stream API에 명시되어 있는데, 해당 API는 명세에 따르면 비동기 반복자가 가능하다. 따라서 무한 반복문 말고 for await ... of 반복문을 사용할 수 있다. 그렇지만 이는 아직 모든 브라우저에서 지원되지 않기에 보통 while을 이용한 무한 루프를 사용한다.
Fetch ReadableStream Step
fetch 메서드를 호출하고 응답을 받는다. 그리고 response.body.getReader()를 호출해서 Stream API를 처리할 수 있는 reader 객체를 생성한다.
데이터를 읽기에 앞서 Content-Length 헤더를 통해 응답 전체의 크기를 미리 구한다. 이는 CORS 이슈로 접근이 불가할 수도 있는데 보통의 경우 문제없이 크기를 구할 수 있다.
데이터를 await reader.read()를 통해 순차적으로 읽는다. 응답 청크를 chunks 배열에 차례대로 삽입한다. 일단 응답을 한 번 소비하고 나면 response.json()과 같은 다른 메서드를 사용할 수 없기 때문에 해당 배열을 가지고 원하는 응답 본문에 접근할 것이다.
모든 데이터를 읽으면 그 값을 가지고 있는 chunks 배열이 만들어진다. 이 배열을 다시 Uint8Array 형식화 배열로 변환한다. 우리가 필요한 것은 단일화 된 데이터 형태인데, 안타깝게도 chunks 배열을 단번에 Uint8Array로 변환하기 위한 단일 메서드는 없다. 따라서 반복문을 통해 순회하며 변환작업을 수행하자.
chunksAll 변수에는 변환이 완료된 결과가 들어있다. 이는 아직 문자열이 아닌 바이트 배열이기 때문에 이를 다시 문자열로 변환해주어야 한다. 이는 TextDecoder를 통해 수행할 수 있다.
최종적으로 TextDecoder를 통해 변환된 결과를 다시 JSON.parse() 메서드를 통해 JSON으로 변환하면 원하는 응답 본문에 commits에 접근할 수 있다.
Unicode
유니코드(Unicode)는 전 세계의 모든 문자를 다루도록 설계된 표준 문자 전산 처리 방식
Promise
Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.
대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
이행(fulfilled): 연산이 성공적으로 완료됨.
거부(rejected): 연산이 실패함.
then
Promise를 리턴하고 두개의 콜백 함수를 인수로 받았을 때, 이행 or 거부를 위한 콜백함수
async & await
funciton 앞에 async를 붙이면, 함수는 항상 Promise를 반환
awiat 키워드를 만나면, Promise가 처리될때까지 기다림
await는 promise.then보다 좀 더 세련되게 프라미스의 result 값을 얻을 수 있도록 해주는 문법입니다. promise.then보다 가독성 좋고 쓰기도 쉽습니다.
cosnt response = await fetch('http://localhost:3000/products');
const reader = response.body.getReader();
const chunk = await reader.read();
// chunk.value는 Uint8Array 타입.
// 원래는 chunk.done이 true일 때까지 반복해야 한다.
const body = new TextDecoder().decode(chunk.value);
const data = JSON.parse(body);
// ReadableStream 객체의 내장 메서드 getReader() 호출
const reader = response.body.getReader();
// 무한 반복문을 돌면서 reader 객체로부터 계속 내용을 읽어옴
while(true) {
// 마지막 청크가 도착할 때 done = true
// value는 청크 바이트의 Uint8Array 형태
const { done, value } = await reader.read();
if (done) {
break;
}
console.log(`수신 : ${value.length} bytes`);
}
// Step 1 : fetch 응답 수신 후 reader 객체 생성
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100');
const reader = response.body.getReader();
// Step 2 : 헤더정보를 통해 전체 크기 구하기
const contentLength = +response.header.get('Content-Length');
// Step 3 : 청크 단위로 데이터 읽기
let receivedLength = 0;
let chunks = [];
while(true) {
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
receivedLength += value.length;
console.log(`수신: ${receivedLength} of ${contentLength}`);
}
// Step 4 : chunks 배열을 단일 Uint8Array 형태로 변환
let chunksAll = new Uint8Array(receivedLength);
let position = 0;
for(let chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}
// Step 5 : 문자열로 디코딩
let result = new TextDecoder('utf-8').decode(chunksAll);
// Step 6 : 결과 출력
let commits = JSON.parse(result);