Introduction
async / await 함수를 araboza
# 일반함수와 다른 점
const fuc = () => {};
const asyncFuc = async () => {};
console.log(fuc);
console.log("일반함수는 ", fuc(), "를 리턴해");
console.log(asyncFuc);
console.log("async함수는 ", asyncFuc(), "를 리턴해");
이 코드를 실행해보자.
일반함수 fuc이랑 asyncFuc이라는 async함수를 만들어줬고 콘솔로 각각 함수객체와 호출후 리턴되는 값을 출력해봤다.
일반함수는 함수객체 출력값이 function으로 나오고 리턴값은 없으니까 undefined를 출력한다.
하지만 async함수는 함수객체가 AsyncFunction으로 나오고 리턴값이 없는데 promise를 출력한다!
여기서 중요한점은 async함수는 promise를 return한다는 점이다!
# return값을 넣어보면?
const asyncFuc = async () => {
return "무찌";
};
console.log("async함수는 ", asyncFuc(), "를 리턴해");
이렇게 Promise{'무찌'}라는 값이 출력된다.
예전에 이걸 잠깐 본적있지않음?
const asyncFuc = async () => {
return "무찌";
};
const returnPromiseFuc = () => {
return new Promise((res) => {
res("무찌");
});
};
console.log("async함수는 ", asyncFuc(), "를 리턴해");
console.log("promise 리턴하는 함수는 ", asyncFuc(), "를 리턴해");
함수를 하나 더 만들어봤음.
promise를 return하는 함수를 만들었는데, promise를 리턴하고 함수가 호출됐을경우 이 프로미스는 fulfilled상태에서 데이터값을 '무찌'를 resolve하게 만들어줬다.
과연 출력결과는?
짜잔~ 동일한 값을 resolve한 fulfilled상태의 promise를 return한다.
여기서 상세히보면 두 함수는 완벽히 같지는 않지만, (함수생성시 동작하는방식이나 에러처리 reject등이 다름..) 암튼 지금 수준에서는 저 두함수는 promise를 return하는 함수란점에서 같다고 봐도 무방함.
promise / Synchronous Processing & Asynchronous Processing
Introduction사실 나도 jsdeepdive를 뒷쪽은 못읽었다. 지금 1년전에 600페이지까지 10회독한게 마지막 ㅠ암튼 그래서 아직 개념은 좀 부족하지만, promise로 동기 비동기 처리는 자주해봤고 얕게는 알기
jacobowl.tistory.com
만약 promise를 이해못하겠으면 이전에 작성한 Promise관련 포스팅을 보세용
# async함수에서 return하는 값은 바로 쓸수 없다.
이걸 몰라서 예전에 샷건을 많이 쳤었다.
const asyncFuc = async () => {
return "무찌";
};
console.log(asyncFuc() + "야! 놀자~!"); //여기서 난 '무찌야! 놀자~! 라는 출력값을 기대함.'
함수에서 return하는 값을 바로 쓰고싶었고 저 console.log출력값으로 async함수가 return하는 무찌라는 스트링과 야!놀자~!라는 스트링이 합쳐져서 무찌야! 놀자~! 하는 스트링이 출력되길 바라는 소망이 있었다.
하지만 잔인하게도 출력값은 오브젝트와 스트링이 멍청하게 합성된 저런 잔인한 스트링이 나와서 너무 슬펐음
console.log(JSON.stringify(asyncFuc()));
이렇게 json으로 저 promise객체를 뜯어서 안에있는 데이터를 쓰는 방식도 될까했는데
빈객체를 출력해주더라..ㅠㅠ
# 그럼 async함수에서 return하는 값은 어떻게 쓸까? - await
const asyncFuc = async () => {
return "무찌";
};
const combineStrings = async () => {
console.log(await asyncFuc()); //async내에서 호출값을 await으로 붙여쓰면 원하는 데이터를 쓸 수 있음.
console.log((await asyncFuc()) + "야! 놀자~!");
};
combineStrings();
combineStrings라는 새로운 async함수를 생성해줬고 이 함수에서 await이라는 예약어로 async함수 return값을 확인해봤다.
아까 위에서 그냥 함수 호출후 return값을 확인했을때는 promise객체가 튀어나왔지만, 또다른 async함수 내에서 await예약어를 붙여서 return값을 확인하니 '무찌'라는 따끈따끈한 스트링이 내뱉어졌다.
떄문에 이 값은 string 타입이기떄문에 원하는대로 무찌야!놀자~!라는 스트링이 잘 합쳐져서 코드가 실행이 잘 되었음.
#
promise개념을 알면 이건 아주 쉽게 알 수 있다.
const asyncFuc = async () => {
return "무찌";
};
const returnPromiseFuc = () => {
return new Promise((res) => {
res("무찌");
});
};
const fuc = () => {
return "무찌";
};
const combineStrings = async () => {
console.log(await asyncFuc());
console.log(await returnPromiseFuc());
console.log(await fuc());
};
combineStrings();
아까 위에서 사용했던 promise를 return하는 일반함수를 또 가져왔다.
그리고 또다른 async함수인 combineStrings라는 함수내에서 await예약어를 붙여서 함수 return값을 확인해보니
세개 다 전부 무찌라는 스트링을 뱉는다.
즉 async함수 내에서 await예약어를 사용하면 promise가 resolve하는 값을 가져온다는 것이다.
일반함수인 fuc도 붙여서 쓸 수 있지만 사실 의미는 없다. promise가 resolve한 값만 되는건지 다른값도 되는지 확인하기 위함이었는데 일단 되긴된다.
즉 promise라면 resolve한 값, 아니라면 그냥 그대로 값을 가져온다는 것임.
# 때문에 async함수는 promise처럼 then을 붙여쓸수 있음.
const asyncFuc = async () => {
return "무찌";
};
asyncFuc().then((data) => {
console.log(data + "가져왔어");
});
'무찌'라는 스트링을 return하는 async함수는 '무찌'라는스트링을 resolve하는 promise와 같다고 보고 asyncFuc()의 호출값 자체에 promise에서 사용했던 .then을 쓸 수 있다.
# settimeout함수로 1초후에 원하는 값을 return하는 async함수를 만들어보자!
const asyncFuc = async () => {
setTimeout(() => {
return "무찌";
});
};
const checkReturnValue = async () => {
console.log((await asyncFuc()) + "받아왔어");
};
checkReturnValue();
자 이렇게하면 내가 원하는 대로 무찌라는 스트링을 갖다 써보자.
자 이럼 asyncFuc함수는 '무찌'라는 스트링을 resolve하는 promise가 리턴되겠지?
ㅅㅂ
여기서 중요한점은 함수스코프와 함수리터럴에 대한 개념이다.
setTimeout(() => {
return "무찌";
});
asyncFuc안에 들어있는 setTimeout함수는 콜백으로 함수리터럴을 받는데, 이 함수리터럴에서 '무찌'라는 스트링을 return 한다.
즉, 우리가 원했던 asyncFuc함수스코프내에서 '무찌'라는 스트링을 return한게 아니라, 저 콜백함수 스코프내에서 return한것이다.
때문에 asyncFuc은 아무것도 리턴을 안하므로 동작을 안하게 됐던것.(정확히는 undefined를 return하는거임)
함수 리터럴에 대해서
# 먼저 리터럴에 대해서 다시 살펴보자. https://jacobowl.tistory.com/137 값(value), 리터럴(literal), 표현식(expression), 문(statement)# 리터럴(literal) 리터럴은 사람이 알아보는 기호로 적은 표기법을 말한다. 이
jacobowl.tistory.com
스코프에 관해서
# 스코프란 무엇일까? {} //블록레벨 스코프? function fuc(){} //함수레벨 스코프? 스코프를 대충 중괄호다 라고 생각하는 사람들도 있을거다. 스코프에 대해서 어떤것인지 정확히 생각해보자. # 스코
jacobowl.tistory.com
스코프와 함수리터럴은 예전 포스팅을 참조하샘.
++ async함수가 return하지 않으면 어떤 상태일까?
위의 예시로 async함수는 아무것도 return하지 않았기 때문에 난 처음에 pending상태일줄 알았는데, 그냥 바로 fulfilled상태가 되고 undefined값을 resolve하는 promise가 되는것이다.
++ 억지로라도 하려면? - 이렇게는 안씀.
const asyncFuc = async () => {
const start = Date.now();
while (Date.now() - start < 1000) {}
return "무찌";
};
함수스코프제한을 막기위해 1초를 기다렸다가 해당 함수스코프내에서 '무찌'를 return하는 함수로 바꿔봄
근데 이렇게는 안쓴다고함. 아마.. 성능떄문인것같음.
# 원하는대로 동작하게 하려면 어떻게 해야할까?
const asyncFuc = async () => {
return new Promise((res) => {
setTimeout(() => {
res("무찌");
}, 1000);
});
};
const checkReturnValue = async () => {
console.log((await asyncFuc()) + "받아왔어");
};
checkReturnValue();
이렇게 async함수가 promise를 return하게 해주고, (정확히는 promise를 resolve하는 promise를 반환하는것) 이 프로미스는 1초후에 '무찌'라는 스트링을 resolve해준다.
때문에 우리가 원하는대로 checkReturnValue라는 async함수에서 1초후에 asyncFuc으로 부터 '무찌'라는 스트링을 받아올 수 있는것이다.
사실
const asyncFuc = () => {
// 이렇게 asyncFuc함수가 프로미스리턴하는 일반함수인게 더 나음
return new Promise((res) => {
setTimeout(() => {
res("무찌");
}, 1000);
});
};
const checkReturnValue = async () => {
console.log((await asyncFuc()) + "받아왔어");
};
checkReturnValue();
그냥 async함수가 어차피 promise를 return하니까 꼭 async함수일 필요가 없다.
이게 promise를 두번하지않으니 성능적으론 더 좋은것같다는 개인적인 생각이 들긴한다.
그래서 gpt한테 물어봤는데, 가독성측면에서는 async를 붙이고, 성능면에서는 안붙여도된다고함. 다만 성능차이는 아주 미세하다고하더라.
# async함수내에서 await은 기다려준다.
const asyncFuc = () => {
return new Promise((res) => {
setTimeout(() => {
res("무찌");
}, 1000);
});
};
const checkReturnValue = async () => {
console.log("무찌는 엉덩이야");
console.log((await asyncFuc()) + "받아왔어");
console.log("무찌는 궁뎅이야");
};
checkReturnValue();
이 코드를 호출해보면
이렇게 무찌는 엉덩이라는 출력이나오고 1초후에 await붙은 async함수의 return값을 받아와서 코드를 실행시켜준다.
또한 이 await 코드가 실행되고나서야 그 아래에 있는 무찌는 궁뎅이라는 출력이 실행된다.
즉 await을 붙이면 그 이후에 코드는 await이 붙은 promise가 fulfilled되기전까지 실행되지 않는다는 소리임.
const asyncFuc = () => {
return new Promise((res) => {
setTimeout(() => {
res("무찌");
}, 1000);
});
};
const checkReturnValue = async () => {
console.log("무찌는 엉덩이야");
await new Promise((res) => {
setTimeout(() => {
console.log("기다림");
res();
}, 1000);
});
console.log("무찌는 궁뎅이야");
};
checkReturnValue();
promise자체에 await을 붙여도 마찬가지로
똑같이 기다려줌.
떄문에 async await은 동기 비동기처리하기에 아주아주 가독성좋게 편리하다.
# async함수에서 promise 동기처리
이전에 동기처리 (직렬처리)하는걸 async await으로 구현해보자.
const random = () => {
return Math.floor(Math.random() * 2001);
};
const callMuzzi = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("무찌야!");
}, random());
});
};
const callDori = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("도리야!");
}, random());
});
};
const callSeokgu = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("석구야ㅠㅠ");
}, random());
});
};
const final = async () => {
await callMuzzi();
await callDori();
await callSeokgu();
};
final();
final이라는 async함수로 세 코드를 동기화해서 해보면
const final = async () => {
await callMuzzi();
await callDori();
await callSeokgu();
};
놀랍도록 단순하게 구현이 된걸 알 수 있다.. ㄷㄷ
# 그럼 async await으로 reject처리는 어떻게 할까?
const random = () => {
return Math.floor(Math.random() * 2001);
};
const callMuzzi = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("무찌야!");
}, random());
});
};
const callDori = () => {
return new Promise((res, rej) => {
setTimeout(() => {
rej("도리에서 에러!"); //도리함수에서 reject를 해보았다.
}, random());
});
};
const callSeokgu = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("석구야ㅠㅠ");
}, random());
});
};
const final = async () => {
try {
await callMuzzi();
await callDori();
await callSeokgu();
} catch (err) {
console.log(err);
}
};
final();
만약 저렇게 callDori함수에서 return하는 promise에서 reject를 해보았다.
그리고 final함수에서 try catch문으로 작성을 해보았다.
출력결과물은?
이토록 promise에서 catch문에서 reject된거 잡듯이 final async함수에서 try catch문의 catch에서 해당 오류를 잡아준다.
또한 마지막의 callSeokgu함수는 호출되지 않았다.
보통은 이걸
const callDori = () => {
return new Promise((res, rej) => {
setTimeout(() => {
rej(new Error("도리에서 에러"));
}, random());
});
};
const final = async () => {
try {
await callMuzzi();
await callDori();
await callSeokgu();
} catch (err) {
console.log(err.message);
}
};
이렇게 에러객체를 reject로 넘겨주거나,
const asyncFuc = async () => {
throw new Error("여기서 에러");
};
const finall = async () => {
try {
await asyncFuc();
} catch (err) {
console.log(err.message);
}
};
finall();
체이닝에있는 async함수에서 에러를 던지게 해서 받아오게 된다.
# 비동기처리(병렬처리)
이제 비동기처리를 해보자.
const random = () => {
return Math.floor(Math.random() * 2001);
};
const callMuzzi = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("무찌야!");
}, 1000);
});
};
const callDori = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("도리야!");
}, 1000);
});
};
const callSeokgu = () => {
return new Promise((res) => {
setTimeout(() => {
res();
console.log("석구야ㅠㅠ");
}, 0);
});
};
const final = async () => {
const callMuzziAndDori = Promise.all([callMuzzi(), callDori()]);
await callMuzziAndDori;
await callSeokgu();
};
final();
비동기처리이기떄문에 명확히 시간적으로 알기위해서 setTimeout함수를 1초로 설정해주었다.
그리고 새로운 식별자를 선언해줬는데, callMuzziAndDori로 callMuzzi()와 callDori()를 promise.all로 묶어주었다.
그리고 await뒤에는 promise가 와야하니 await callMuzziAndDori로 기다려주었고
이 작업을 기다린후 await callSeokgu가 실행되도록 해줬다.
출력결과를 보면, 무찌랑 도리가 동시에 출력되고 바로 석구가 출력된다.
Promise.all로 살짝 눈갱을 당했지만 그래도 훨씬 보기 편해졌지?
# 괴랄한 실습 다시 해보자
이거 async await으로 한번 다시 해볼까?
const random = () => {
return Math.floor(Math.random() * 2001);
};
function CreateFuc(data) {
this.data = data;
}
CreateFuc.prototype.createFunction = function () {
return (arr) => {
return new Promise((res, rej) => {
setTimeout(() => {
res(this.data);
}, random());
});
};
};
const fuc1 = new CreateFuc("data1").createFunction();
const fuc2 = new CreateFuc("data2").createFunction();
const fuc3 = new CreateFuc("data3").createFunction();
const fuc4 = new CreateFuc("data4").createFunction();
const fuc5 = new CreateFuc("data5").createFunction();
const fuc6 = new CreateFuc("data6").createFunction();
const final = async () => {
const promise_top = async () => {
const top_arr = [];
const fuc12 = Promise.all([fuc1(), fuc2()]);
(await fuc12).forEach((data) => {
top_arr.push(data);
});
top_arr.push(await fuc3());
return top_arr;
};
const promise_bot = async () => {
const bot_arr = [];
bot_arr.push(await fuc4());
bot_arr.push(await fuc5());
return bot_arr;
};
const topbot_Promise = Promise.all([promise_top(), promise_bot()]);
const arr = (await topbot_Promise).flat();
arr.push(await fuc6());
console.log(arr);
};
final();
더 간결해진건진 모르겠지만.. 암튼 비동기 병렬처리는 promise.all로 묶어줬고, 그냥 동기처리는 await으로 넘겨줌.
이전 promise포스팅에선 이전 promise에서 받은 데이터 자체를 처리하기위해 조건문으로 어레이냐 아니냐에 따라서 resolve값도 다르게 했지만 이번에는 해당 기능을 없애고 단순히 data+숫자 데이터만 넘겨지는 생성자함수로 짰다.
끗
Conclusion
async await을 사용하기위해선 promise개념은 필수불가결이다.
또한 단순히 async await만 사용하지 않고, promise문법도 같이 사용을 해야한다.
때문에 Synchronous Processing & Asynchronous Processing포스팅 시리즈를 시작할때 사실 난 promise와 async await을 같이 자주 쓴다고 한것이다.
암튼 이 포스팅을 본 사람들은 나처럼 키보드샷건치면서 어려워하지 않았으면 좋겠다.
내포스팅도 과거의 내가 봤으면 아마 샷건쳤을것같은 느낌이 들지만..ㅋㅋㅋ
'javaScript > concept' 카테고리의 다른 글
promise / Synchronous Processing & Asynchronous Processing (0) | 2024.11.06 |
---|---|
Synchronous Processing & Asynchronous Processing (0) | 2024.11.06 |
객체, 배열 반복문의 종류 / 어레이 순환 프로토타입 메서드 (2) | 2023.05.10 |