JavaScript - 비동기, Promise

uk·2022년 12월 6일

JavaScript

목록 보기
18/19

Promise란?

  • 자바스크립트에서 비동기 처리를 위해 콜백 함수를 사용하였다. 하지만 콜백 함수를 반복해서 사용하면 콜백 지옥이 발생하는 문제점이 있었고 이를 해결하기 위해 Promise를 사용하는 방법이 있다. Promise는 자바스크립트 비동기 처리에 사용되는 객체이다.
  • Promise를 사용하면 비동기 처리 시점을 명확하게 표현할수 있고 연속된 비동기 처리 작업을 쉽게 확인할 수 있으며 추가, 수정, 삭제 작업을 유연하게 처리할 수 있다.

Promise 사용하기

let promise = new Promise((resolve, reject) => {
	// 정상적으로 처리되는 경우, resolve의 인자에 값을 전달할 수도 있다.
	resolve(value);

	// 에러가 발생하는 경우, reject의 인자에 에러 메세지를 전달할 수도 있다.
	reject(error);
});
  • Promise는 class이기 때문에 new 키워드를 통해 Promise 객체를 생성한다.
  • 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달받는데 이 콜백 함수는 resolve, reject 함수를 인자로 전달받는다.
  • Promise 객체가 생성되면 executor는 자동으로 실행되고 작성했던 코드들이 작동된다. 코드가 정상적으로 처리가 되었다면 resolve 함수를 호출하고 에러가 발생했을 경우에는 reject 함수를 호출한다.

Promise의 세가지 상태

프로미스의 상태란 프로미스의 처리 과정을 의미하며 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖는다.

  • Pending(대기) : 비동기 처리 로직이 완료되지 않은 상태
  • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환한 상태
  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

Pending(대기)

new Promise((resolve, reject) => {
	... 
});
  • new Promise() 메서드를 호출하면 대기 상태가 된다. 비동기 처리 로직이 완료되지 않은 상태이며 콜백 함수를 선언할 수 있고 인자로 resolve, reject를 받는다.

Fulfilled(이행)

new Promise((resolve, reject) => {
  resolve("success");
});
  • 콜백 함수의 인자 resolve를 호출하면 이행 상태가 된다. 비동기 처리가 완료된 상태로 이행 상태가 되면 .then 메서드로 결과값을 받을 수 있다.

Rejected(실패)

new Promise((resolve, reject) => {
  reject("failure");
});
  • 콜백 함수의 인자 reject를 호출 실행하면 실패 상태가 된다. 비동기 처리를 실패하거나 오류가 발생한 상태로 실패 상태가 되면 .catch 메서드로 결과값을 받을 수 있다.

Promise 객체의 내부 프로퍼티

new Promise가 반환하는 Promise 객체는 state, result 내부 프로퍼티를 갖는다. 하지만 직접 접근할 수 없고 .then, .catch, .finally 메서드를 사용해야 접근이 가능하다.

  • State - 기본 상태는 pending(대기) 이며 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동했다면 fulfilled(이행)로 변경이 되고 에러가 발생했다면 rejected(거부)가 된다.

  • Result - 초기값은 undefined 이며 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동하여 resolve(value)가 호출되면 value로, 에러가 발생하여 reject(error)가 호출되면 error로 변한다.


.then

let promise = new Promise((resolve, reject) => {
	resolve("success");
});

promise.then(value => {
	console.log(value);  // "success"
})
  • executor에 작성했던 코드들이 정상적으로 처리가 되었다면 resolve 함수를 호출하고 .then 메서드로 접근할 수 있다.
  • .then 안에서 리턴한 값이 Promise라면 내부 프로퍼티인 result를 다음 .then의 콜백 함수의 인자로 받아오고, Promise가 아니라면 리턴한 값을 .then의 콜백 함수의 인자로 받아올 수 있다.

.catch

let promise = new Promise(function(resolve, reject) {
	reject(new Error("error!"))
});

promise.catch(error => {
	console.log(error);  // Error: error!
})
  • executor에 작성했던 코드들이 에러가 발생했을 경우에는 reject 함수를 호출하고 .catch 메서드로 접근할 수 있다.

.finally

let promise = new Promise(function(resolve, reject) {
	resolve("success");
});

promise
.then(value => {
	console.log(value);  // "success"
})
.catch(error => {
	console.log(error);
})
.finally(() => {
	console.log("성공이든 실패든 작동");	 // "성공이든 실패든 작동"
})
  • executor에 작성했던 코드들의 정상 처리 여부와 상관없이 .finally 메서드로 접근할 수 있다.

Promise chaining

let promise = new Promise(function(resolve, reject) {
	resolve('success');
	...
});

promise
  .then((value) => {
    console.log(value);
    return 'success';
  })
  .then((value) => {
    console.log(value);
    return 'success';
  })
  .then((value) => {
    console.log(value);
    return 'success';
  })
  .catch((error) => {
    console.log(error);
    return 'failure';
  })
  .finally(() => {
    console.log('성공이든 실패든 작동!');
  });
  • Promise chaining이 필요한 경우는 비동기 작업을 순차적으로 진행해야 하는 경우이다. Promise chaining이 가능한 이유는 .then, .catch, .finally 메서드들이 Promise를 반환하기 때문이다. 따라서 .then을 통해 연결할 수 있고 에러가 발생할 경우 .catch로 처리한다.

Promise.all()

const promiseOne = () => new Promise((resolve, reject) => setTimeout(() => resolve('1초'), 1000));
const promiseTwo = () => new Promise((resolve, reject) => setTimeout(() => resolve('2초'), 2000));
const promiseThree = () => new Promise((resolve, reject) => setTimeout(() => resolve('3초'), 3000));

// 기존
const result = [];
promiseOne()
  .then(value => {
    result.push(value);
    return promiseTwo();
  })
  .then(value => {
    result.push(value);
    return promiseThree();
  })
  .then(value => {
    result.push(value);
   console.log(result);	 // ['1초', '2초', '3초']
  })
  
// promise.all
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
  .then((value) => console.log(value))  // ['1초', '2초', '3초']
  .catch((err) => console.log(err));
  • Promise.all()은 여러 개의 비동기 작업을 동시에 처리하고자 할때 사용한다. 배열을 인자로 받고 해당 배열에 있는 모든 Promise에서 executor 내 작성했던 코드들이 정상적으로 처리가 되었다면 결과를 배열에 저장해 새로운 Promise를 반환 한다.

  • Promise chaining을 사용했을 경우는 코드들이 순차적으로 동작되기 때문에 위의 예시에서 총 6초의 시간이 걸리고 같은 코드가 중복되는 현상도 발생하게 된다.

  • Promise.all()을 통해 이러한 문제들을 해결할 수 있으며 비동기 작업들을 동시에 해결하기 때문에 총 3초의 시간이 걸린다. 또한 Promise chaining으로 작성한 코드보다 간결해진다.


Promise.all([
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('error1'))), 1000),
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('error2'))), 2000),
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('error3'))), 3000),
])
	.then((value) => console.log(value))
  .catch((err) => console.log(err));  // Error: error1
  • Promise.all은 인자로 받는 배열의 Promise 중 하나라도 에러가 발생하게 되면 나머지 Promise의 state와 상관없이 즉시 종료된다. 예시와 같이 1초 후에 에러가 발생하고 Error: error1이 반환되면 더 이상 작동하지 않고 종료된다.

Promise Hell

  • Promise를 통해 비동기 코드의 순서를 제어할 수 있지만 콜백 함수와 마찬가지로 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생한다.

0개의 댓글