ES6-18 반복자 생성 - async 함수
카탈로그
async 함수
속뜻
ES2017 표준은 async 함수를 도입하여 비동기 조작을 더욱 편리하게 하였다.
async 함수는 무엇입니까?한마디로 Generator 함수의 문법 사탕이다.const gen = function* () {
const f1 = yield console.log('1');
const f2 = yield console.log('2');
};
// `gen` `async` , 。
const asyncReadFile = async function () {
const f1 = await console.log('1');
const f2 = await console.log('2');
};
비교해 보면 async
함수는 Generator 함수의 별표*
)를 async
로, yield
를 await
로 바꾸는 것이다.async
함수가 Generator 함수에 대한 개선은 다음과 같은 네 가지에 나타난다.
(1) 내장된 실행기.async
함수 자체 실행기.즉 async
함수의 집행은 일반 함수와 똑같고 한 줄만 있으면 된다는 것이다.asyncReadFile();
위의 코드가 asyncReadFile
함수를 호출한 후에 자동으로 실행되고 마지막 결과를 출력합니다.이것은 완전히 Generator 함수와 같지 않아서 next
방법을 호출해야만 진정으로 집행하고 최종 결과를 얻을 수 있다.
(2) 더 좋은 의미.async
와await
는 별표와yield
보다 의미가 더 명확해졌다.async
는 함수에 비동기적인 조작이 있음을 나타내고await
는 뒤에 뒤따르는 표현식이 결과를 기다려야 한다는 것을 나타낸다.
(3) 반환 값은 Promise입니다.async
함수의 반환값은Promise 대상이며, 이것은Generator 함수의 반환값보다 Iterator 대상이 훨씬 편리하다.너는 then
방법으로 다음 조작을 지정할 수 있다.
더 나아가 async
함수는 여러 개의 비동기적인 조작으로 볼 수 있고 하나의 Promise 대상으로 포장될 수 있다. await
명령은 내부then
명령의 문법 설탕이다.
기본용법
async
함수는 프로미스 대상을 되돌려줍니다. then
방법으로 리셋 함수를 추가할 수 있습니다.함수가 실행될 때 await
를 만나면 먼저 되돌아와 비동기적인 조작이 완성될 때까지 기다린 다음에 함수 체내 뒤의 문장을 실행한다.
다음은 몇 밀리초를 지정한 후에 값을 출력하는 예입니다.function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
위 코드가 50밀리초를 지정한 후 출력hello world
.async
함수는 Promise 대상을 되돌려주기 때문에 await
명령의 매개 변수로 사용할 수 있습니다.그래서 위의 예도 아래의 형식으로 쓸 수 있다.async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
async 함수는 여러 가지 사용 형식이 있다.//
async function foo() {}
//
const foo = async function () {};
//
let obj = { async foo() {} };
obj.foo().then(...)
// Class
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('jake').then(…);
//
const foo = async () => {};
구문
async
함수의 문법 규칙은 전체적으로 비교적 간단하고 어려운 점은 오류 처리 메커니즘이다.
Promise 객체로 돌아가기
async
함수는 Promise 객체를 반환합니다.async
함수 내부return
문장이 되돌아오는 값은 then
방법 리셋 함수의 매개 변수가 된다.async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
위 코드에서 함수f
내부return
명령이 되돌아오는 값은 then
방법의 리셋 함수에 의해 수신됩니다.async
함수 내부에서 오류가 발생하면 되돌아오는 Promise 대상reject
상태가 됩니다.던진 오류 대상은 catch
방법 리셋 함수에 의해 수신됩니다.async function f() {
throw new Error(' ');
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error:
Promise 객체의 상태 변경
async
함수가 되돌아오는 Promise 대상은 내부 모든await
명령 뒤에 있는 Promise 대상이 실행될 때까지 기다려야 상태 변화가 발생합니다. return
문장이나 버퍼링 오류가 발생하지 않는 한.즉 async
함수 내부의 비동기 조작이 끝나야만 then
방법이 지정한 리셋 함수를 실행할 수 있다는 것이다.
await 명령
정상적으로 await
명령 뒤에는 Promise 객체가 있고 해당 객체의 결과가 반환됩니다.Promise 객체가 아니면 해당 값을 반환합니다.async function f() {
//
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
위 코드에서 await
명령의 매개 변수는 수치123
인데 이때는 return 123
와 같다.
또 다른 경우await
명령 뒤에 thenable
대상(즉 정의then
방법의 대상)이 있으면await
은 이를 Promise 대상과 동일시한다.JavaScript는 항상 휴면 문법이 없지만 await
명령을 사용하면 프로그램이 지정한 시간을 멈출 수 있습니다.다음은 간략한sleep
실현을 제시했다.function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}
//
async function one2FiveInAsync() {
for(let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000);
}
}
one2FiveInAsync();
await
명령 뒤의 Promise 대상이 reject
상태가 되면 reject
방법의 리셋 함수에 의해 catch
인자가 수신됩니다.async function f() {
await Promise.reject(' ');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
//
주의, 위 코드에서 await
문장 앞에는 return
가 없지만 reject
방법의 매개 변수는 catch
방법의 리셋 함수로 전송되었다.여기에 await
앞에 return
를 더하면 효과가 같다.await
문장 뒤에 있는 Promise 대상이 reject
상태가 되면 전체 async
함수가 실행을 중단합니다.async function f() {
await Promise.reject(' ');
await Promise.resolve('hello world'); //
}
위 코드에서 두 번째await
문장은 실행되지 않습니다. 첫 번째await
문장 상태가 reject
로 바뀌었기 때문입니다.
때때로, 우리는 이전의 비동기 조작이 실패하더라도, 뒤의 비동기 조작을 중단하지 않기를 바란다.이때 첫 번째await
를 try...catch
구조에 넣으면 이 비동기 조작이 성공하든 안 하든 두 번째await
가 실행될 수 있다.async function f() {
try {
await Promise.reject(' ');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
또 다른 방법은 await
뒤에 있는 Promise 대상과 catch
방법을 따라 앞에 발생할 수 있는 오류를 처리하는 것이다.async function f() {
await Promise.reject(' ')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
//
// hello world
오류 처리
await
뒤에 있는 비동기 동작이 잘못되면 async
함수가 되돌아오는 Promise 대상reject
과 같다.async function f() {
await new Promise(function (resolve, reject) {
throw new Error(' ');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:
위 코드에서 async
함수f
가 실행되면 await
뒤에 있는 Promise 대상이 오류 대상을 던져서 catch
방법의 리셋 함수를 호출하는데 그 매개 변수는 바로 던진 오류 대상이다.
오류를 방지하는 방법도 try...catch
코드 블록에 넣는다.async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error(' ');
});
} catch(e) {
}
return await('hello world');
}
만약 여러 개await
명령이 있다면 try...catch
구조에 통일적으로 놓을 수 있다.async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
아래의 예는 try...catch
구조를 사용하여 여러 차례의 반복 시도를 실현했다.const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
위 코드에서 await
조작이 성공하면 break
문장을 사용하여 순환을 종료합니다.실패하면 catch
문에 스냅되어 다음 순환에 들어갑니다.
주의점 사용
첫째, 앞에서 말했듯이 await
명령 뒤의 Promise
대상은 실행 결과rejected
일 수 있으므로 await
명령을 try...catch
코드 블록에 넣는 것이 가장 좋다.async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
//
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
둘째, 여러 개await
의 명령 뒤에 있는 비동기 조작은 계발 관계가 존재하지 않으면 동시에 촉발하는 것이 좋다.let foo = await getFoo();
let bar = await getBar();
위 코드에서 getFoo
와 getBar
는 두 개의 독립된 비동기 조작(즉 서로 의존하지 않음)으로 계발 관계로 쓰였다.이렇게 하면 getFoo
가 완성된 후에만 실행할 수 있기 때문에getBar
완전히 동시에 촉발할 수 있다.//
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
//
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
위의 두 가지 쓰기 방법getFoo
과 getBar
은 모두 동시에 터치하여 프로그램의 실행 시간을 단축시킨다.
세 번째, await
명령은 async
함수에만 사용할 수 있고 일반 함수에 사용하면 오류가 발생합니다.async function dbFuc(db) {
let docs = [{}, {}, {}];
//
docs.forEach(function (doc) {
await db.post(doc);
});
}
위의 코드는 await
일반 함수에 사용되었기 때문에 잘못 보고될 것이다.그러나 forEach
방법의 매개 변수를 async
함수로 바꾸는 것도 문제가 있다.function dbFuc(db) { // async
let docs = [{}, {}, {}];
//
docs.forEach(async function (doc) {
await db.post(doc);
});
}
위 코드가 정상적으로 작동하지 않을 수도 있습니다. 왜냐하면 이때 세 개의 db.post
조작이 동시에 실행될 것입니다. 즉, 동시에 실행되는 것이지, 계속 실행되는 것이 아닙니다.정확한 쓰기 방법은 for
순환을 채택하는 것이다.async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
여러 개의 요청이 동시에 실행되기를 원한다면 Promise.all
방법을 사용할 수 있습니다.세 가지 요청이 모두 resolved
될 때, 아래의 두 가지 쓰기 효과는 같다.async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
//
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
다른 비동기 처리 방법과 비교
우리는 하나의 예를 통해 async 함수와Promise,Generator 함수의 비교를 보았다.
DOM 요소 위에 이전 애니메이션이 끝나야 다음 애니메이션이 시작된다고 가정합니다.애니메이션 중 하나가 잘못되면 더 이상 아래로 내려가지 않고 마지막으로 성공적으로 실행된 애니메이션의 되돌아오는 값을 되돌려줍니다.
먼저 Promise 쓰기function chainAnimationsPromise(elem, animations) {
// ret
let ret = null;
// Promise
let p = Promise.resolve();
// then ,
for(let anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
// Promise
return p.catch(function(e) {
/* , */
}).then(function() {
return ret;
});
}
프로미스의 쓰기는 리셋 함수의 쓰기보다 크게 개선되었지만 한눈에 보면 코드가 완전히 프로미스의 API(then
,catch
등)로 조작 자체의 의미를 오히려 알아내기 어렵다.
그 다음은 async 함수의 쓰기 방법입니다.async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* , */
}
return ret;
}
Async 함수의 실현이 가장 간결하고 의미에 가장 부합되며 의미와 상관없는 코드가 거의 없다는 것을 알 수 있다.Generator 쓰기 방법의 자동 실행기를 언어 차원에서 제공하여 사용자에게 노출되지 않기 때문에 코드량이 가장 적습니다.Generator Write를 사용하는 경우 자동 실행기는 사용자가 직접 제공해야 합니다.
const gen = function* () {
const f1 = yield console.log('1');
const f2 = yield console.log('2');
};
// `gen` `async` , 。
const asyncReadFile = async function () {
const f1 = await console.log('1');
const f2 = await console.log('2');
};
asyncReadFile();
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
//
async function foo() {}
//
const foo = async function () {};
//
let obj = { async foo() {} };
obj.foo().then(...)
// Class
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('jake').then(…);
//
const foo = async () => {};
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
async function f() {
throw new Error(' ');
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error:
async function f() {
//
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}
//
async function one2FiveInAsync() {
for(let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000);
}
}
one2FiveInAsync();
async function f() {
await Promise.reject(' ');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
//
async function f() {
await Promise.reject(' ');
await Promise.resolve('hello world'); //
}
async function f() {
try {
await Promise.reject(' ');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
async function f() {
await Promise.reject(' ')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
//
// hello world
async function f() {
await new Promise(function (resolve, reject) {
throw new Error(' ');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error(' ');
});
} catch(e) {
}
return await('hello world');
}
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
//
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
let foo = await getFoo();
let bar = await getBar();
//
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
//
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
async function dbFuc(db) {
let docs = [{}, {}, {}];
//
docs.forEach(function (doc) {
await db.post(doc);
});
}
function dbFuc(db) { // async
let docs = [{}, {}, {}];
//
docs.forEach(async function (doc) {
await db.post(doc);
});
}
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
//
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
function chainAnimationsPromise(elem, animations) {
// ret
let ret = null;
// Promise
let p = Promise.resolve();
// then ,
for(let anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
// Promise
return p.catch(function(e) {
/* , */
}).then(function() {
return ret;
});
}
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* , */
}
return ret;
}