본문 바로가기
  • hello world
Language/Javascript

[JS] Promise와 async/await

by JJoajjoa 2024. 7. 30.

 

JavaScript에서 Promise :
비동기 작업의 완료 또는 실패를 나타내는 객체
비동기 작업이 성공하면 작업의 결과 값으로, 실패하면 오류 이유로 Promise 객체를 사용할 수 있음
Promise는 콜백 대신 비동기 작업을 처리하는 더 나은 방법을 제공함

 

 

세가지 주요 상태

 

  • Pending (대기 중): 초기 상태, 비동기 작업이 아직 완료되지 않음.
  • Fulfilled (이행됨): 비동기 작업이 성공적으로 완료됨.
  • Rejected (거부됨): 비동기 작업이 실패함.

 

 

let promise = new Promise((resolve, reject) => {
    // 비동기 작업 수행
    let success = true;

    if (success) {
        resolve("작업 성공");
    } else {
        reject("작업 실패");
    }
});

 

Promise 객체는 생성자 함수 Promise를 사용하여 생성할 수 있음

이 생성자는 resolve와 reject 함수를 인수로 받는 콜백 함수를 인수로 받음

 

 

promise
    .then(result => {
        console.log(result);  // "작업 성공"
    })
    .catch(error => {
        console.error(error);  // "작업 실패"
    })
    .finally(() => {
        console.log("작업 완료");
    });

 

 

  • then 메서드: 작업이 성공했을 때 호출될 콜백 함수를 인수로 받음
  • catch 메서드: 작업이 실패했을 때 호출될 콜백 함수를 인수로 받음
  • finally 메서드: 작업의 성공 또는 실패 여부에 상관없이 항상 실행될 콜백 함수를 인수로 받음

 

 

 

Promise 체이닝

function fetchData1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve("데이터1 가져오기 성공"), 1000);
    });
}

function fetchData2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve("데이터2 가져오기 성공"), 1000);
    });
}

fetchData1()
    .then(result => {
        console.log(result);
        return fetchData2();
    })
    .then(result => {
        console.log(result);
    })
    .catch(error => {
        console.error(error);
    })
    .finally(() => {
        console.log("작업 완료");
    });

여러 비동기 작업을 순차적으로 수행하려면 then 메서드를 체이닝할 수 있음

 

 

 

예시 ▼ 2초 후에 무작위로 성공 또는 실패를 시뮬레이션

let asyncTask = new Promise((resolve, reject) => {
    setTimeout(() => {
        let success = Math.random() > 0.5;

        if (success) {
            resolve("작업 성공");
        } else {
            reject("작업 실패");
        }
    }, 2000);
});

asyncTask
    .then(result => {
        console.log(result);
    })
    .catch(error => {
        console.error(error);
    })
    .finally(() => {
        console.log("작업 완료");
    });

 

 

▼ 위와 같은 예시이지만 async와 await을 사용함

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = Math.random() > 0.5;
            if (success) {
                resolve("데이터 가져오기 성공");
            } else {
                reject("데이터 가져오기 실패");
            }
        }, 2000);
    });
}

async function fetchDataAsync() {
    try {
        const result = await fetchData();
        console.log(result);
    } catch (error) {
        console.error(error);
    } finally {
        console.log("작업 완료");
    }
}

fetchDataAsync();

 

이 예제에서 fetchDataAsync 함수는 async로 정의되어 있음

await 키워드를 사용하여 fetchData 함수가 반환하는 Promise가 이행될 때까지 기다리게 됨

try, catch, finally 블록을 사용하여 비동기 작업의 결과를 처리

async/await를 사용하면 비동기 코드를 더 읽기 쉽고, 동기 코드처럼 보이게 작성할 수 있음

 

 

  • async : 함수 선언 앞에 사용되며, 해당 함수가 비동기 함수임을 나타냄(이 함수는 항상 Promise를 반환)
  • await : Promise가 해결되거나 거부될 때까지 함수 실행을 일시 중지(await는 async 함수 안에서만 사용할 수 있음)

 

 

 


 

왜 굳이 동기 코드처럼 보이게 작성할까?

 

async/await를 사용하여 비동기 코드를 동기 코드처럼 보이게 작성하는 데에는 몇 가지 주요 이점이 있습니다:

 

 

가독성과 유지보수성 향상

1. 직관적인 흐름: async/await를 사용하면 코드가 위에서 아래로 읽히며, 명령문이 순차적으로 실행되는 것처럼 보입니다. 이는 비동기 코드의 복잡성을 줄이고, 코드의 흐름을 더 쉽게 이해할 수 있게 합니다.

async function process() {
    try {
        const data1 = await fetchData1();
        const data2 = await fetchData2();
        console.log(data1, data2);
    } catch (error) {
        console.error(error);
    }
}

 

2. 에러 처리 간소화: try/catch 블록을 사용하여 동기 코드에서처럼 비동기 코드에서도 에러를 처리할 수 있습니다. Promise 체이닝에서 .catch()를 여러 번 사용하는 것보다 더 간단하고 직관적입니다.

async function fetchDataAsync() {
    try {
        const result1 = await fetchData1();
        const result2 = await fetchData2();
        console.log(result1, result2);
    } catch (error) {
        console.error(error);
    }
}

 

 

콜백 지옥 (Callback Hell) 해결

복잡한 중첩 제거: Promise 이전에는 비동기 코드를 작성할 때 콜백 지옥(Callback Hell)이라고 불리는 코드 중첩 문제가 발생했습니다. Promise로 개선되었지만, 여전히 복잡한 체이닝이 필요한 경우에는 코드가 복잡해질 수 있습니다. async/await는 이러한 문제를 완화합니다.

// Callback Hell 예시
fetchData1((data1) => {
    fetchData2(data1, (data2) => {
        fetchData3(data2, (data3) => {
            console.log(data3);
        });
    });
});

// Promise 체이닝
fetchData1()
    .then(data1 => fetchData2(data1))
    .then(data2 => fetchData3(data2))
    .then(data3 => console.log(data3))
    .catch(error => console.error(error));

// async/await
async function fetchDataAsync() {
    try {
        const data1 = await fetchData1();
        const data2 = await fetchData2(data1);
        const data3 = await fetchData3(data2);
        console.log(data3);
    } catch (error) {
        console.error(error);
    }
}

 

 

코드 유지보수와 디버깅 용이성

  1. 코드 유지보수: 코드가 간결하고 명확하면 유지보수가 더 쉬워집니다. 개발자는 동작 방식을 쉽게 이해하고, 필요한 수정 사항을 신속하게 적용할 수 있습니다.
  2. 디버깅: async/await는 디버깅이 더 쉽습니다. 디버깅할 때 비동기 호출이 마치 동기 호출처럼 한 줄씩 실행되기 때문에, 실행 흐름을 따라가기가 더 수월합니다.

 

 

비동기 작업 간의 의존성 처리

비동기 작업 간에 의존성이 있는 경우, async/await는 이를 더 명확하게 처리할 수 있습니다. 예를 들어, 두 번째 비동기 작업이 첫 번째 작업의 결과를 필요로 하는 경우, async/await를 사용하면 이를 더 명확하게 표현할 수 있습니다.

async function process() {
    const result1 = await fetchData1();
    const result2 = await fetchData2(result1); // result1이 필요함
    console.log(result2);
}

 

 

 


 

 

//일반함수
function add1(a, b) {
    return a + b;
}
const result = add1(10, 10);
console.log(`더하기 결과: ${result}`);
//화살표함수
const add2 = (a, b) => {
    return a + b;
}
//콜백함수
const add3 = (a, b, callback) => {
    const result = a + b;
    callback(result);
}
add3(10, 10, (result) => {
    console.log(`콜백함수 안에서의 더하기 결과: ${result}`);
})
//콜백함수
// const divide = (a, b, callback) => {
//     const result = a / b;
//     callback(result);
// }
// divide(100, 0, (result) => {
//     console.log(`콜백함수 안에서의 나누기 결과: ${result}`);
// }) //Infinity가 출력됨 -> 에러라고 표시하고싶은데..
const divide1 = (a, b, callback) => {
    if (b == 0) {
        callback('두번째 값이 없어용', null);
        return;
    }
    const result = a / b;
    callback(null, result);
}
divide1(100, 0, (err, result) => {
    if (err) {
        console.error(`에러발생: ${err}`);
        return;
    }
    console.log(`콜백함수 안에서의 나누기 결과: ${result}`);
})
//아 이거 너무 복잡한데...
//콜백함수를 쓸수밖에ㅓㅄ는 상황에서는 어짜피 써야함
//setTimeout 같은 함수
//브라우저에서 요청 -> 웹서버 응답
//1.장비랑케이블이엄청연결되어잇음 중간에장비를엄청마니거침
//중간에장비성능떨어지면응답이언제올지몰라
//응답을받아서콜백함수를실행한다고할때 콟백이언제실행될지알수강벗ㄷ어
//2.관계형DB 테이블로만드는이유 찾기편하게하기위해
//db가 실제로는 서버처럼 요청가능 응답받을수잇음
//응답이언제올지몰라
//예시를 만들어보자
const divide2 = (a, b, callback) => {
    setTimeout(() => {
        if (b == 0) {
            callback('두번째 값이 없어용', null);
            return;
        }
        const result = a / b;
        callback(null, result);
    }, 1000)
}
divide2(100, 10, (err, result) => {
    if (err) {
        console.error(`에러발생: ${err}`);
        return;
    }
    console.log(`콜백함수 안에서의 나누기 결과: ${result} 68라인`);
})
const add4 = (a, b, callback) => {
    setTimeout(() => {
        const result = a + b;
        callback(null, result);
    }, 500);
}
add4(10, 10, (err, result) => {
    console.log(`콜백함수 안에서의 더하기 결과: ${result} 77라인`);
})
//출력결과가 77라인이 먼저 나옴
//순서대로 실행되야하는게 맞지만 setTimeout으로 인해 시간 차이가 생겨버림
//68라인:1초뒤 | 77라인:0.5초뒤
//이래서 서버 응답이 순서대로 오지 않을 수도 있다
//
//리턴은 결과가 아래쪽ㄱ구멍으로 떨어지게 만들어줌 -> 결과가 나올때까지 대기함
//대기하지않도록하게만들어주는방법이 콜백함수
//웹서버는 요청이 들어오는데로 큐 에 쌓아둠
//언제요청햇는지에따라순서대로처리
//그과정이 성능이 너무떨어짐
//요청들어와? 실행먼저해 들어오는데로 걍 실행해(콜백등록해)
//각각ㅇㅣ결과가만들어지면그때응답해

//나누기결과를가지고더해주고싶다면?
divide2(100, 10, (err, result) => {
    if (err) {
        console.error(`divide2 에러발생: ${err}`);
        return;
    }
    console.log(`콜백함수 안에서의 나누기 결과: ${result}`);

    add4(result, 10, (err, result2) => {
        if (err) {
            console.error(`divide2 add4 에러발생: ${err}`);
            return;
        }
        console.log(`콜백함수 안에서의 더하기 결과: ${result2}`);
    })
})
//다른 개발자가 만든거 우리는 사용만 할 수 잇어
//더하기나 나누기 결과가 언제올지몰라
//근데 나눈결과로 더해야해
//그럼 이런 형태가 나올수밖에없음
//
//순서대로하고싶으면 콜백 안에 다른 함수를 넣을 수 밖에 없음
//계속 들여쓰기되는 형태
//들여쓰기가 10개까지 늘어나는 상황이 있을 수 있음
//이거 너무 별론데 정리 함 해야겠는데
//
//정리 함 해주자고
// Promise -> 사용을 최대한 단순하게 만든 방법 -> async awite
//
//결론: 
//응답이 언제 올지모르는상황이 웹서버에서는 빈번하다
//그 응답을 순서대로 처리해주고 싶다
//그럴때 사용하는 async awite <- 꼭 알아둬야 함!

// Promise 사용해보기
//복잡해보이지만 간단해지고있는 과정
const addFunc = (a, b) => new Promise((resolve, reject) => {
    add4(a, b, (err, result) => {
        if (err) {
            reject(err);
            return;
        }
        resolve(result);
    })
})
// Promise는 중괄호 없어도 됨
const divideFunc = (a, b) => new Promise((resolve, reject) => {
    divide1(a, b, (err, result) => {
        if (err) {
            reject(err);
            return;
        }
        resolve(result);
    })
})
// 그러면 이렇게 간단하게 바뀔수있음
const doCalc = async () => {
    try {
        const result1 = await divideFunc(200, 10); //콜백이아니게됨
        console.log(`나눈결과: ${result1}`);
        const result2 = await addFunc(result1, 10);
        console.log(`더한결과: ${result2}`);
    }
    catch (err) {
        console.error(`doCalc에서 에러 발생: ${err}`);
    }
}
doCalc();