비동기 함수를 사용하면 마치 동기 함수인 것처럼 프로미스 기반 코드를 작성할 수 있습니다.
비동기 함수는 Chrome, Edge, Firefox, Safari에서 기본적으로 사용 설정되어 있습니다. 솔직히 상당히 놀라운 것입니다. 이를 통해 프라미스 기반 코드를 동기적이었지만 기본 스레드를 차단하지 않은 경우. 그들은 덜 '똑똑한' 비동기 코드 더 쉽게 읽을 수 있습니다.
비동기 함수는 다음과 같이 작동합니다.
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
함수 정의 앞에 async
키워드를 사용하면
함수 내의 await
. 프로미스를 await
하면 함수가 일시중지됩니다.
프라미스가 해결될 때까지 비블로킹 방식으로 작동합니다. 프라미스가 이행되면
반환합니다. 프라미스가 거부되면 거부된 값이 발생합니다.
브라우저 지원
브라우저 지원
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
예: 가져오기 로깅
URL을 가져오고 응답을 텍스트로 로깅한다고 가정해 보겠습니다. 다음과 같이 표시됩니다. 사용할 수 있습니다.
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);
});
});
}
제이크 '프라미스 행사자' 좀 보세요. 아치볼드. 내 전화 사용 방법 보기
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 루프로 바뀌었습니다. 훨씬 낫습니다. 앞으로는
비동기 반복자,
그 것은
while
루프를 for-of 루프로 교체하여 더 깔끔하게 만듭니다.
기타 비동기 함수 문법
이미 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, 000ms가 걸립니다.
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밀리초가 걸립니다. 두 대기가 동시에 발생하기 때문입니다. 실제 예를 살펴보겠습니다.
예: 가져오기(fetch)를 순서대로 출력
일련의 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)할 수 있습니다.
Babel이 이 작업을 대신 수행합니다. Babel REPL을 통한 예시
- 트랜스파일된 코드가 얼마나 비슷한지 주목하세요. 이 변환은 Babel의 es2017 프리셋
트랜스파일 방식을 추천합니다. 대상 브라우저가 비동기 함수를 지원하지만 실제로 트랜스파일러를 사용하면 Babel의 폴리필 직접 사용해 보세요. 다음을 대신해서 사용합니다.
async function slowEcho(val) {
await wait(1000);
return val;
}
polyfill을 포함하면 됩니다. 그리고 다음과 같이 작성합니다.
const slowEcho = createAsyncFunction(function* (val) {
yield wait(1000);
return val;
});
생성기 (function*
)를 createAsyncFunction
에 전달해야 합니다.
그리고 await
대신 yield
를 사용합니다. 그 외에는 동일하게 작동합니다.
해결 방법: 리제너레이터
오래된 브라우저를 대상으로 하는 경우 Babel은 생성기를 트랜스파일할 수도 있습니다. IE8까지 비동기 함수를 사용할 수 있게 해줍니다. 이렇게 하려면 Babel의 es2017 프리셋 및 es2015 프리셋도 있습니다.
출력이 그렇게 예쁘지 않으므로 있습니다.
모든 것을 비동기화합니다.
모든 브라우저에서 비동기 함수가 지원되면 프라미스 반환 함수! 코드를 더 깔끔하게 만들 뿐만 아니라 함수가 항상 프로미스를 반환하도록 합니다.
저는 비동기 함수에 다시 와서 2014년 실제로 브라우저에서 방문하게 되어 매우 기쁩니다. 와!