2022. 11. 23 스터디 내용
참고
https://ko.javascript.info/async
프라미스와 async, await
ko.javascript.info
동기, 비동기
자바스크립트는 기본적으로 동기식(synchronous)으로 동작합니다. 동기란 한 번에 한 가지 작업만 하는 것을 말합니다. 자바스크립트는 한 작업을 완전히 끝낸 후에, 다음 작업을 실행합니다. (=싱글태스킹)
console.log('1');
setTimeout(()=>console.log('2'), 5000);
console.log('3')

반대로, 비동기식(asynchronous)은 동시에 여러가지 일을 수행합니다. 하나의 작업에 대한 동작이 끝나지 않아도 바로 다음 작업을 이어서 실행합니다. (=멀티태스킹)
주로 언제 끝날지 모르는 작업, 통신 등에 사용합니다.
console.log('1');
setTimeout(()=>console.log('2'), 5000);
console.log('3')

콜백
콜백 함수(callback function)는 나중에 실행되는 함수를 말합니다. 콜백 함수는 호출 방법(함수의 매개변수로 또 다른 함수를 전달하여 사용하는 형태)에 의한 구분입니다. 아래 코드에서 A 함수 자체는 콜백 함수가 아니지만, B 함수의 매개변수가 됨으로써 콜백 함수가 됩니다.
🔽 콜백 함수의 기본적인 형태
let A = function(){ //함수 정의
console.log('A function')
}
function B(callback){
console.log('B function 1')
callback(); //개발자가 A()를 직접 호출하지 않음. 흐름에 따라 시스템이 호출함.
console.log('B function 2')
}
B(A);
중첩 콜백 함수
콜백 함수를 중첩해서 사용할 수도 있습니다. 하지만 아래 코드와 같이 들여쓰기가 반복되다보면, 코드의 깊이가 감당할 수 없을 정도까지 깊어질 수 있습니다. 이러한 현상을 콜백 지옥(callback hell)이라고 부릅니다.
콜백 지옥은 프라미스(promise)를 적용함으로써 해결할 수 있습니다.
예제
🔽 콜백함수로 익명 함수를 사용하고 있음
function count(cnt, callback){
setTimeout(()=>{
console.log(cnt)
cnt++;
callback(cnt);
}, 1000)
}
count(1, function(result){
count(result, function(result){
count(result, function(result){
count(result, function(result){
count(result, function(result){
console.log(result); //6 출력
})
})
})
})
});
프라미스 (Promise)
프라미스는 다음과 같은 문법으로 생성할 수 있습니다.
let promise = new Promise(function(resolve, reject) {
//코드
});
new Promise로 전달되는 함수는 실행자, 실행함수(executor)라고 부르며, promise 생성과 동시에 실행됩니다.
resolve와 reject는 자바스크립트에서 제공하는 콜백 함수이므로 개발자가 정의할 필요는 없습니다. 실행함수의 코드를 수행한 결과에 따라서 resolve 또는 reject 콜백 함수가 실행됩니다.
- resolve(value) : 일이 성공적으로 끝난 경우, 그 결과를 나타내는 value와 함께 호출
- reject(error) : 에러 발생 시 주로 호출 (주로 에러 객체와 함께 호출)
❗ 반드시 둘 중 하나를 실행해야합니다.
new Promise()를 함으로써 생성되는 Promise 객체는 다음과 같은 속성을 가집니다.
- state : 처음엔 pending(보류)이었다가 / resolve가 호출되면 fulfilled, reject가 호출되면 rejected로 변합니다.
- result : 처음엔 undefined이었다가 / resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변합니다.

처리된 프라미스
동작이 모두 끝난 프라미스는 처리된 프라미스(settled promise)라고 부릅니다. 이는 호출되는 함수(resole, reject)에 따라서 둘로 나뉩니다.
1. 약속이 이행된 프라미스
작업이 성공적으로 수행된 경우, 약속이 이행된 프라미스(fulfilled promise)라고 부릅니다.
let promise = new Promise(function(resolve, reject) {
console.log('짜잔')
setTimeout(() => resolve("결과"), 1000);
});

2. 약속이 거부된 프라미스
에러를 발생하는 경우, 약속이 거부된 프라미스(rejected promise)라고 부릅니다.
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("error 발생")), 1000);
});

소비함수: then, catch, finally
프라미스는 실행함수와 소비함수를 연결하는 역할을 합니다. 소비함수는 실행함수 결괏값/에러를 받습니다. then, catch, finally 메서드를 사용하여 등록할 수 있습니다.
then
프라미스의 기본이 되는 메서드입니다. then의 첫번째 인수는 ‘약속이 이행된 프라미스일 경우의 동작’이며, 두번째 인수는 ‘약속이 거부된 프라미스일 경우의 동작’을 나타냅니다.
promise.**then**(
function(result) { console.log("fulfilled") }, //첫번째 인수
function(error) { console.log("rejected") } //두번째 인수
)
인수를 하나만 사용하면, 약속이 이행된 프라미스에 대한 처리만을 할 수 있습니다.
반대로, 약속이 거부된 프라미스에 대한 처리만을 하고싶다면 promise.then(null, error)와 같이, 첫번째 인수값을 null로 둘 수 있습니다.
catch
약속이 거부된 프라미스에 대한 처리만을 하고싶다면, catch 메서드를 사용할 수도 있습니다.
promise.**catch**(
function(error){ .. }
)
finally
처리된 프라미스의 결과와 상관없이 무조건 실행하고싶다면 finally 메서드를 사용합니다. 실행 결과는 다음 소비함수로 넘어갑니다.
❗finally는 인수를 가지지 않습니다. 프라미스가 이행되었는지, 거부되었는지 알아야할 필요 자체가 없으니까요!
promise.finally(
function( ) { console.log("finally ~~~") }
)
프라미스 체이닝
순차적으로 처리해야 하는 비동기 작업은 프라미스 체이닝(promise chaining)을 통해서 해결할 수 있습니다. 이것이 가능한 이유는 소비함수(.then 등)가 프라미스를 반환하기 때문입니다.
소비함수에 사용된 핸들러 함수가 프라미스를 반환하는 경우도 있습니다. 이런 경우에, 이어지는 핸들러는 프라미스가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받습니다.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
})
.**then**(function(result) {
console.log(result) //1
**return new Promise**((resolve, reject) => {
setTimeout(() => resolve(++result), 1000);
});
})
.then(function(result) {
console.log(result) //2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(++result), 1000);
});
})
.then(function(result) {
console.log(result) //
});
cf. 핸들러 함수는 프라미스가 아닌 thenable 객체를 반환하기도 합니다. .then 메서드를 가진 객체를 모두 thenable 객체라고 칭합니다. 이는 프라미스와 같은 방식으로 처리됩니다.
프라미스 API
Promise 클래스는 5개의 정적 메서드를 가집니다.
Promise.all
여러개의 프라미스를 동시에 실행시키고, 모든 프라미스가 처리될 때까지 대기합니다. 이터러블 객체를 파라미터로 가집니다.
결괏값들을 배열형태로 반환합니다. 만약, 거부된 프라미스가 하나라도 발생한다면, 전체를 거절합니다.
🔽 Promise.all()의 기본 형태
let promise = **Promise.all**([...promises...]);
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); //
Promise.allSettled
Promise.all과 달리, 거부된 프라미스가 발생하더라도 모든 프라미스가 처리될 때까지 대기합니다.
Promise.allSettled는 각 프라미스의 상태와 결괏값/에러를 배열 형태로 반환합니다. 배열의 각 요소는 다음과 같습니다
- 응답이 성공할 경우 : {status:"fulfilled", value:결괏값}
- 에러가 발생한 경우 : {status:"rejected", reason:에러객체}
Promise.race
promise.all과 비슷하지만, 가장 먼저 처리되는 프라미스의 결과/에러를 반환한다는 특징이 있습니다. 다른 프라미스들의 결과는 무시합니다.
Promise.race([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); //1 출력
Promise.resolve
Promise.resolve(value)는 결괏값이 value인 이행된 프라미스를 생성합니다. 이는 아래 코드와 같은 역할을 합니다.
let promise = new Promise(resolve => resolve(value));
호환성을 위해서 일반 함수가 프라미스를 반환해야하는 경우에 사용합니다.
Promise.reject
Promise.reject(error)는 결괏값이 error인 거부된 프라미스를 생성합니다.
❗ Promise.resolve와 Promise.reject는 async/await가 나타나며 거의 사용되지 않고 있습니다.
프라미스화
콜백을 받는 함수를 프라미스를 반환하는 함수로 바꾸는 것을 '프라미스화(promisification)'라고 합니다.
❗ async/await를 사용하여, 보다 편리하게 프라미스화 하는 방법도 있습니다.
예제
🔽 콜백함수
function count(cnt, callback){
setTimeout(()=>{
console.log(cnt)
cnt++;
callback(cnt);
}, 1000)
}
count(1, function(result){
count(result, function(result){
count(result, function(result){
count(result, function(result){
count(result, function(result){
console.log(result); //6 출력
})
})
})
})
});
🔽 프라미스화
function count(cnt){
return new Promise(function(resolve){
setTimeout(()=>{
console.log(cnt);
resolve(cnt);
}, 1000)
})
}
count(1)
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
async와 await
async/await를 사용하면 편리하게 프라미스화를 할 수 있습니다.
async
function 앞에 async를 붙인 함수는 반드시 promise를 반환합니다. 만약 프라미스가 아닌 것을 반환하려한다면, 프라미스로 감싸 반환합니다.
async function promise() {
return "결과";
});
promise().then(alert); //"결과"
await
자바스크립트는 await를 만나면 프라미스가 처리될 때까지 기다립니다. 프라미스가 처리되었다면, 값을 받고, 다음 코드를 실행합니다.
async function promise() {
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("결과"), 1000);
});
let result = **await** promise; //"결과"
});
❗ await는 async 함수에만 사용할 수 있습니다.
여러개의 프라미스가 모두 실행될 때까지 기다려야한다면, Promise.all과 await를 함께 사용할 수 있습니다.
예제
🔽 프라미스화
function count(cnt){
return new Promise(function(resolve){
setTimeout(()=>{
console.log(cnt);
resolve(cnt);
}, 1000)
})
}
console.log("시작");
count(1)
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
.then(function(result){
return count(++result)
})
.finally(function(){
console.log("끝")
})
🔽 async/await 사용
function count(cnt){
return new Promise(function(resolve){
setTimeout(()=>{
console.log(cnt);
resolve(cnt);
}, 1000)
})
}
async function run(){
console.log("시작");
let result = 1;
console.log(result);
await count(++result);
await count(++result);
await count(++result);
await count(++result);
await count(++result);
console.log("끝");
}
run();
메모
- 모든 프라미스는 thenable이지만 모든 thenable이 프라미스는 아니다!
'그 외' 카테고리의 다른 글
| [pgAdmin][Error] FATAL: connection requires a valid client certificate (0) | 2025.09.16 |
|---|---|
| [langchain] document loader (0) | 2025.09.15 |
| [JS] 제네레이터와 비동기 이터레이션 (0) | 2025.09.08 |
| [Git] 기초 (0) | 2025.09.03 |