본문 바로가기
카테고리 없음

비동기 프로그래밍 - new Promise

by cariño 2022. 10. 16.
728x90
반응형

Promise

promise 객체는 아래와 같은 문법으로 만든다.

let promise = new Promise(function(resolve, reject){
    // executor
});

 

프라미스는 성공 또는 실패만 한다.

new Promise

state: pending

result: undefined

 

[resolve]
state: fullfilled
result: value

 

[reject]
state: rejected
result: error
 
executor는 promise의 상태를 둘 중 하나로 변화시킨다. 
  • resolve(value) — 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
  • reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출
const pr = new Promise((resolve, reject) => {
    // 어떤 일이 완료된 후 실행 되는 함수 : callback

    setTimeout(() => {
        resolve('ok') //fullfilled
        reject(new Error('error..')) //rejected
    }, 3000)
})

 

[resolve·reject 함수 즉시 호출하기]

 

executor는 대개 무언가를 비동기적으로 수행하고, 약간의 시간이 지난 후에 resolve, reject를 호출하는데, 꼭 이렇게 할 필요는 없습니다. 아래와 같이 resolve reject를 즉시 호출할 수도 있다.
let promise = new Promise(function(resolve, reject) {
  // 일을 끝마치는 데 시간이 들지 않음
  resolve(123); // 결과(123)를 즉시 resolve에 전달함
});
 

 

then, catch, finally

 

promise 객체의 state, result 프로퍼티는 내부에 있다.

then, catch, finally를 사용하면 접근이 가능하다.

 

 

[then]

.then은 프라미스에서 가장 중요하고 기본이 되는 메서드이다.

.then의 첫 번째 인수는 프라미스가 이행되었을 때 실행되는 함수이고, 여기서 실행 결과를 받는다.

.then의 두 번째 인수는 프라미스가 거부되었을 때 실행되는 함수이고, 여기서 에러를 받는다.

작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달하면 된다.

//then을 이용해서 pr의 인수로 reject와 resolve를 처리할 수 있다. 
pr.then(
    function (result) { } - 이행되었을 때
    function (err) { } - 거부되었을 때
)​

 

[catch]

에러가 발생한 경우만 다루고 싶다면 catch

catch .then null을 전달하는 것과 동일하게 작동한다.

 

[fianlly]

try {...} catch {...}에 finally 절이 있는 것처럼, 프라미스에도 finally가 있다.

쓸모가 없어진 로딩 인디케이터(loading indicator)를 멈추는 경우같이, 결과가 어떻든 마무리가 필요하면 finally가 유용하다.

▼ //then과 catch, finally를 사용할 수 있다.

pr.then(
    function (result) { }
).catch( //가독성 편으로 좋다.
    function (error) { }
).finally(// reject, resolve 처리가 완료되면 항상 실행 됨 
    function () {
        console.log('---종료---')
    }
)

[비동기 처리를 위한 콜백 패턴의 단점]


왜 promise를 쓰게됐는지, callback hell의 모습을 살펴보자.

// callback hell
const f1 = (callback) => {
    setTimeout(() => {
        res('1번 주문 완료')
        callback()
    }, 1000);
}

const f2 = (callback) => {
    setTimeout(() => {
        res('2번 주문 완료')
        callback()
    }, 1000);
}

const f3 = (callback) => {
    setTimeout(() => {
        res('3번 주문 완료')
        callback()
    }, 1000);
}

console.log('시작')
f1(function () {
    f2(function () {
        f3(function () {
            console.log('종료')
        })
    })
})

 

[Promise chainng]

해당 콜백 헬 로직을 Promise 객체로 바꿔보기

//promise로 구현

const f1 = () => {
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('1번 주문 완료')
        }, 1000);
    })
}

const f2 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('2번 주문 완료')
        }, 1000);
    })
}

const f3 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('3번 주문 완료')
        }, 1000);
    })
}

console.log('시작')
f1()
    .then(res => f2(res))
    .then(res => f3(res))
    .then(res => console.log(res))
    .catch(console.log)
    .finally(() => {
        console.log('끝')
    })

Promise.all

 

모두 한꺼번에 이행시킬 수 있다.

프라미스가 이행될 때까지 기다리다 그 결과 값을 담은 배열을 반환하는 메서드이다.

여러개의 프로미스가 모두 resolve됐을 때 바로 다음 로직을 실행하는 경우에 사용한다.

promise.all()을 사용할 때 주의해야할 점은 배열 내 요소 중 어느 하나라도 거부하면 즉시 거부한다.

 

* 복수의 URL에 request를 보내고, 모든 요청의 응답이 올때 화면을 랜더 해야하는 상황이 그 예시이다.

요청에 필요한 정보를 배열로 저장한 뒤, 그후 해당 정보를 프로미스로 매핑하여 Promise.all()에 입력하는

방법이 자주 쓰인다.

// promise all
console.time('x')
Promise.all([f1(), f2(), f3()]) //세 작업이 모두 완료되어야지만 then부분이 실행됨
    .then(res => {
        console.log(res)
        console.timeEnd('x')
    })

 

Promise.race

 

Promise.race()는 가장 빨리 응답을 받은 결과값만 resolve한다.

가장 빨리 도착한 promise의 result만이 .then() 구문으로 넘어갈 수 있다.

// promise.race

console.time('x')
Promise.race([f1(), f2(), f3()])
    .then(res => {
        console.log(res)
        console.timeEnd('x')
    })

//1번 주문 완료
x: 1.015s

// 차이점: 하나라도 1등으로 완료되면 종료시킴

 

 


async, await

promise를 좀 더 편하게 사용할 수 있다.

[async]
async를 붙인 함수는 Promise를 반환한다.
함수 바로 뒤에 then으로 호출 할 수 있다.
async function getName() {
    return 'Mike'
}
// 그래서 함수를 호출하고 바로 .then을 붙일 수 있다.
getName().then(name => {
    console.log(name) //Mike
})
 
 
 

만약에 함수의 반환값이 Promise일경우에는?
Promise의 값을 그대로 사용한다.

async function getName2() {
    return Promise.resolve('Tom')
}
getName2().then(name => {
    console.log(name) //Tom
})
 
 
 
함수내부에서 예외가발생하면 reject가 반환된다.
그렇게 되면 함수 뒤 then으로 호출이 아닌 catch로 확인 할 수 있다.
async function getName3() {
    throw new Error('error...')
}
getName3().catch(err => {
    console.log(err) //Error: error...
})
 
 
 
[await]
async함수 내부에서만 사용 가능하다.
await 키워드 뒤에는 Promise가 오고, 해당 Promise객체가 처리될때까지 기다린다.
function getName(name) {
    return new Promise((resolve, rej) => {
        setTimeout(() => {
            resolve(name)
        }, 1000)
    })
}

async function showName() {
    const result = await getName('mike')
    console.log(result)
}

console.log('시작')
showName()
//시작
//(1초뒤) mike

 


 

[Promise로 구현된 함수들을 async await을 이용해보자.]

 

asycn await 내부에서도 비동기 함수를 병렬로 실행할 수 있다.

result란 변수에 함수안의 데이터들이 기다렸다가 들어가는 상황을 볼 수 있다.
그래서 promise .then구문보다 가독성이 더 좋다. 

const f1 = () => {
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('1번 주문 완료')
        }, 1000);
    })
}

const f2 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('2번 주문 완료')
        }, 1000);
    })
}

const f3 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('3번 주문 완료')
        }, 1000);
    })
}


console.log('시작')
async function order() {
    const result1 = await f1()
    const result2 = await f2(result1)
    const result3 = await f3(result2)
    console.log(result3)
    console.log('끝')
}
order()
//시작
// 1번 주문 완료
// 2번 주문 완료
// 3번 주문 완료
// 끝

 

 

[try.. catch]


만일 reject가 있으면? 에러가 난 후 코드가 종료된다.

이럴경우 promise는 catch를 사용했는데 async awiat은 try.. catch문을 사용할 수 있다.
try문을 사용하고 에러가 났을 경우 catch문을 사용한다.
에러를 처리해주고 다음 코드가 실행된다.

...

const f2 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            rej(new Error('err...'))
        }, 1000);
    })
}

const f3 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('3번 주문 완료')
        }, 1000);
    })
}


console.log('시작')
async function order() {
    try {
        const result1 = await f1()
        const result2 = await f2(result1)
        const result3 = await f3(result2)
    } catch (e) {
        console.log(e)
    }
    console.log('끝')
}
order()

//시작
//1번 주문 완료
//Error: err...
//끝

 

Promise.all을 사용할 때?

const f1 = () => {
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('1번 주문 완료')
        }, 1000);
    })
}

const f2 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('2번 주문 완료')
        }, 1000);
    })
}

const f3 = (message) => {
    console.log(message)
    return new Promise((res, rej) => {
        setTimeout(() => {
            res('3번 주문 완료')
        }, 1000);
    })
}


console.log('시작')
async function order() {
    try {
        const result = await Promise.all([f1(), f2(), f3()])
        console.log(result)
    } catch (e) {
        console.log(e)
    }
    console.log('끝')
}
order()

//시작
//[ '1번 주문 완료', '2번 주문 완료', '3번 주문 완료' ]
//끝

 

728x90

댓글