async function fetchAndPrint(){
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const result = await response.text();
console.log(result);
}
fetchAndPrint();
await -> 어떤 프로미스 객체를 리턴하는 코드 앞에 붙여져 있음
async : 비동기, 함수 안에 비동기적으로 실행될 부분이 있다는 것을 의미
await : 그 뒤의 코드를 실행하고 리턴하는 프로미스 객체를 기다려줌 (프로미스 객체가 fulfilled 상태 또는 rejected 상태가 될 때까지)
fulfilled 상태가 되면 작업 성공 결과를 리턴해서 돌려줌
await은 async가 붙은 함수 안에서만 쓸 수 있다.
async function fetchAndPrint(){
console.log(2);
const response = await fetch('https://jsonplaceholder.typicode.com/users');
console.log(7);
const result = await response.text();
console.log(result);
}
console.log(1);
fetchAndPrint();
console.log(3);
console.log(4);
console.log(5);
console.log(6);
fetchAndPrint 함수는 동기 실행처럼 생겼지만 비동기 실행 함수이다.
이렇게 생긴 이유는 async/await 구문 자체가 기존의 Promise 객체를 사용하는 코드(Promise Chaining)를
(1) 개발자가 더 편하게 작성할 수 있도록 하고
(2) 코드의 가독성을 높이기 위해서
도입된 Syntactic sugar(기존 문법을 더 편하게 사용할 수 있도록 하는 문법적 장치)에 해당하기 때문이다.
async function fetchAndPrint(){
try{
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const result = await response.text();
console.log(result);
} catch (error) {
console.log(error);
} finally {
console.log('exit');
}
}
fetchAndPrint();
async 함수 안에서 리턴하는 값의 종류에 따라 결국 어떤 Promise 객체를 리턴하게 되는지 살펴보겠다.
async 함수 안에서 Promise 객체를 리턴하는 경우에는 해당 Promise 객체와 동일한 상태와 작업 성공 결과(또는 작업 실패 정보)를 가진 Promise 객체를 리턴한다.(그냥 해당 Promise 객체를 리턴한다고 봐도 괜찮음)
async function fetchAndPrint() {
return new Promise((resolve, reject)=> {
setTimeout(() => { resolve('abc'); }, 4000);
});
}
fetchAndPrint();
async 함수 내부에서 Promise 객체 이외에 숫자나 문자열, 일반 객체 등을 리턴하는 경우에는, fulfilled 상태이면서, 리턴된 값을 작업 성공 결과로 가진 Promise 객체를 리턴한다.
async function fetchAndPrint() {
console.log('Hello Programming!');
}
fetchAndPrint();
fulfilled 상태이면서, undefined를 작업 성공 결과로 가진 Promise 객체가 리턴된다.
async function fetchAndPrint() {
throw new Error('Fail');
}
fetchAndPrint();
async 함수 안에서 에러가 발생하면, rejected 상태이면서, 해당 에러 객체를 작업 실패 정보로 가진 Promise 객체가 리턴된다.
async 함수는 항상 Promise 객체를 리턴
async 함수 안에 async 함수를 넣을 수 있다.
다른 함수 안에 넣을 때, 함수 앞에 await을 붙이면 됨
// 1) Function Declaration
async function example1(a, b) {
return a + b;
}
// 2-1) Function Expression(Named)
const example2_1= async function add(a, b) {
return a + b;
};
// 2-2) Function Expression(Anonymous)
const example2_2 = async function(a, b) {
return a + b;
};
// 3-1) Arrow Function
const example3_1 = async (a, b) => {
return a + b;
};
// 3-2) Arrow Function(shortened)
const example3_2 = async (a, b) => a + b;
즉시 실행 함수의 경우에는 다음과 같이 async를 붙일 수 있다.
(async function print(sentence) {
console.log(sentence);
return sentence;
}('I love JavaScript!'));
(async function (a, b) {
return a + b;
}(1, 2));
(async (a, b) => {
return a + b;
})(1, 2);
(async (a, b) => a + b)(1, 2);
async function getResponses(urls) {
for(const url of urls){
const response = await fetch(url);
console.log(await response.text());
}
}
순차적인 작업 처리를 하는 코드이다. (이전 URL에 대해서 await 문이 붙은 Promise 객체가 fulfilled 상태가 될 때까지는 그 다음 URL에 대한 작업들이 시작될 수 없기 때문에)
하지만 출력 순서는 상관없이 모든 리스폰스의 내용이 출력되기만 하면 된다면, 이 코드는 성능 관점에서 아쉬운 코드이다.
async function fetchUrls(urls){
for(const url of urls){
(async () => { // 추가된 부분!
const response = await fetch(url);
console.log(await response.text());
})(); // 추가된 부분!
}
}
이렇게 코드를 고치면 모든 URL에 대한 리퀘스트를 쭉 보내버리고, 먼저 리스폰스가 오는 순서대로 그 내용이 출력된다.
순차적인 처리가 필요한 경우가 아니라면 각 작업을 다시 async 함수로 묶어주면 된다.
setTimeout, setInterval 함수, DOM 객체의 addEventListener 메소드 등
setTimeout(() => {
console.log('asynchronously executed');
}, 2000);
button.addEventListener('click', (event) => { console.log('You Clicked'); });
함수의 파라미터로 콜백을 바로 전달하는 방식은 많은 경우에 쓰이고 있지만,
여러 비동기 작업의 순차적인(sequential) 처리가 필요한 경우에는 콜백 헬이라는 문제가 발생할 수도 있다.
fs.readFile('file1.txt', 'utf8', (error1, data1) => {
if (error1) {
console.log(error1);
} else {
console.log(data1);
fs.readFile('file2.txt', 'utf8', (error2, data2) => {
if (error2) {
console.log(error2);
} else {
console.log(data2);
fs.readFile('file3.txt', 'utf8', (error3, data3) => {
if (error3) {
console.log(error3);
} else {
console.log(data3);
}
});
}
});
}
});
fetch('https://www.google.com')
.then((response) => response.text())
.then((result) => { console.log(result); })
.catch((error) => { console.log(error); })
.finally(() => { console.log('exit'); });
Promise 객체를 사용하면 콜백 헬 문제를 방지하면서, 여러 비동기 작업을 순차적으로 처리할 수 있다.
function readFile_promisified(filename) {
const p = new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (error, data) => {
if (error) {
reject(error); // 에러 발생 시 -> rejected
} else {
resolve(data); // 파일 내용 읽기 완료 -> fulfilled
}
});
});
return p;
}
기존의 1.과 같은 전통적인 비동기 실행 함수들 중에서도 그 콜백이 단 한 번만 실행되는 함수들은 이런 식으로 Promisify해서 콜백 헬의 가능성을 없애고, Promise Chain 안에서 그 콜백의 리턴값을 사용할 수 있다.
async function fetchAndPrint() {
try {
const response = await fetch('https://www.google.www');
const result = await response.text();
console.log(result);
} catch(error) {
console.log(error);
} finally {
console.log('exit');
}
}
fetchAndPrint();
async/await 구문은 Promise 객체를 다루는 코드(Promise Chaining 코드 등)를 사람들이 좀더 익숙하게 느끼는 동기 실행 스타일의 코드로 작성할 수 있게 해주는 Syntactic sugar이다.
async 함수 안의 내용이 순차적으로 실행되다가도, await 문을 만나면 await 바로 뒤에 붙은 코드를 실행해두고, 일단은 함수 바깥으로 코드 흐름이 바뀐다.
자바스크립트에서 콜백은 어떤 함수의 파라미터로 전달되는 모든 함수를 의미하는 개념이기 때문에, 어떤 함수의 파라미터로 전달되기만 한다면 해당 함수는 그 함수의 콜백이 된다.