[자바스크립트] Promise

박은정·2021년 11월 12일
1

자바스크립트

목록 보기
15/25
post-thumbnail

Promise를 사용하는 이유

  • 비동기처리를 통한 콜백지옥같은 자바스크립트의 비효율적인 처리를 극복하기 위함이다

Promise 기본형태

  • ES6에서 도입된 개념
  • 자바스크립트의 내장 클래스

아직 클래스를 다루진 않았지만,
클래스와 인스턴스화 클래스는 객체를 만드는 공장 같은 것으로,
이러한 공장에서 객체를 만들 때 인스턴스화한다고 한다.

  • 자바스크립트에서 비동기처리를 간편하게 처리할 수 있도록 제공하는 객체

  • 정해진 기능을 수행했다면 결과값을 반환하고, 처리하지 못했을 때에는 error를 반환한다

  • Promise 클래스를 인스턴스화해서 promise 객체를 만든다

  • 이렇게 만든 promise 객체를 통해 비동기 작업을 한다.

Promise는 클래스이기 때문에 new 생성자를 통해 인스턴스화해서 객체를 만든다.

let promise = new Promise(function(resolve, reject) {
  // 비동기로직
});

state

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

Promise 처리결과에 따른 작업

then의 콜백함수 handler function 는 여러 타입의 반환이 가능하다

1. 일반적인 return값

첫 번째 then의 return값이 그 다음 then의 콜백함수의 매개변수로 들어간다

let promise = new Promise(function(resolve, reject) {
    // 비동기상황
    setTimeout(function() {
        resolve(1); // 1초뒤에 성공하면 실행
    }, 1000);
});

promise
	.then(function(first) {
	  console.log('first', first);
	  return 2;
	}).then(function(second) { 
		console.log(second);
	});

2. promise 반환

반환된 promise 안에 있는 resolve는 그 다음에 있는 then 안에 있는 콜백함수이다.

let promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve(1);
    }, 1000);
});

promise
	.then(function(first) {
	  console.log('first', first);
	  return 2;
	})
	.then(function(second) { 
		console.log('second', second);
		
    // 두 번째 then의 콜백함수에서 promise를 return 한다
		return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(3);
        }, 1000);
    });
	})
	.then(function(third) {
		console.log('third', third);
	});

3. resolve와 reject

콜백함수 종류특징
resolve비동기 로직이 성공했을 때 실행
reject비동기 로직이 실패했을 때 실행

1. promise 객체의 then이나 catch 메서드를 통해 resolve, reject 함수를 정의한다

resolve, reject 또한 함수이기 때문에 미리 정의를 해야한다고 생각할 수 있겠지만,
미리 정의하지 않아도 자바스크립트 엔진에서 정의해놓기 때문에 에러가 발생하지 않는다
아예 정의를 하지 않는다는 것이 아니라 이후에 .then() 메서드에서 첫 번째 인자로 resolve, 두 번째 인자로 reject 함수를 정의한다

만약 API 호출에 성공했을 때 받은 응답값을 토대로 차트를 그리겠다고 하면,
promise 객체에 then 메서드 를 통해 차트를 그리는 로직을 써 넣으면 된다

let promise = new Promise(function(resolve , reject) {
  setTimeout(function() {
    resolve('hello world'); // 로직이 성공했을 때
    reject('good bye'); // 로직이 실패했을 때 + 단 여기서는 실행되지 않는다
  }, 2000);
});

promise.then(function(msg) {
  console.log('resolve', msg);
}, function(msg) {
  console.log('reject', msg);
});

// 실행결과 : resolve hello world

2. resolve, reject는 비동기 콜백함수이기 때문에 처음에 호출된 한 문장 만 실행한다.

3. Promise 내부의 비동기 로직이 실패했을 때 error 객체를 통해 reject 콜백함수에 전달한다.

  • error 클래스는 자바스크립트에서 제공하는 객체 중 하나이다
  • 에러 오브젝트에 대해 따로 다뤄야하겠지만, 보통은 에러가 발생한 이유에 대해 적는다
const promise = new Promise(resolve, reject) => {
  console.log('doing something...');
  setTimeout(() => reject(new Error('no network')), 2000);
}

promise
  .then(value => {
    console.log(value);
})
  .catch(error => {
    console.log(error);
  })
  .finally(()=> console.log('finally'));

코드설명

  1. then을 호출하게 되면 다시 promise가 리턴되고, 리턴된 promise에 catch를 등록한다
  2. finally 메서드 내부의 콜백함수는 Promise 비동기작업이 성공하던 실패하던 모두 마지막으로 호출하는 함수이다.

4. Promise Chaining

여러 개의 비동기 작업을 순차적으로 해야 할 때 사용한다

예시

순차적으로 각각의 작업이 이전 단계의 비동기 작업이 성공하고 나서 그 결과값을 이용해 다음 비동기 작업을 실행하는 경우

  1. 상품목록 API 호출
  2. 상품목록 API 호출되었을의 id를 가지고 상품후기 API 호출
  3. 상품후기 API를 통한 좋아요수 API
getProducts()
	.then(getComments)
	.then(getLikes)

기본적인 형태

  • then으로 계속 연결하면 된다 : then의 첫 번째 인자는 resolve인 것은 맞지만, 누구의 resolve인지 아는 것이 중요하다
  • then 안에 있는 함수 resolve 콜백함수 에서 어떻게 반환하냐에 따라 다르게 처리할 수도 있다
  • 반환되는 것이 일반값이라면 그 다음 then의 콜백함수의 매개변수이고, promise라면 그 다음 then의 콜백함수는 resolve가 된다.

5. Promise 에러처리

  • 비동기 작업 중에서는 언제 에러가 발생할지 모르기 때문에 개발자가 의도적으로 에러가 발생한다는 상황을 가정해서 reject를 호출 하거나 reject를 호출해서 then의 두 번째 인자를 정의 하는 것 대신, 가독성이 좋은 catch 를 사용하는 것이 대부분이다.
  • 해당하는 곳에서 발생하는 에러를 처리하고 싶은 경우, 그 다음 줄에 바로 catch 메서드를 통해 처리해주면 된다.
  • 전달받은 error를 다른 값으로 대체해서 전체적인 Promise chaining에 문제가 발생하지 않도록 처리할 수 있다.

Promise 에러처리 방법

  • reject
  • catch

catch를 통한 Promise 에러처리

getHen()
.then(getEgg) // 여기서 만약 에러가 발생한다면
.catch(error => {  // 다음 줄 catch를 통해 에러처리가능
  return 'bread'
})
.then(cook)
.then(console.log);
.catch(console.log);

// 실행결과 : bread => fried edd

💡 받아온 하나의 valuw를 콜백함수에서 매개변수로 바로 사용한다면, 생략할 수 있다

// ====생략 전 ====
getHen()
.then(hen => getEgg(hen))
.then(egg => cook(egg))
.then(meal => console.log(meal));

// ====생략 후 ====
getHen() // prettier 에서 한줄로 되기 때문에 주석표시를 해주면 여러줄이 된다
.then(getEgg)
.then(cook)
.then(console.log);

Promise 주의점

1. 동기함수 및 비동기함수 혼용

실행순서는 동기함수 → 비동기함수 이기 때문에
결과적으로 콘솔창에 1, 3, 2 순서로 찍히게 된다

let promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log(1);
        resolve(2);
        console.log(3);
        resolve(4);
    }, 1000);
});

promise.then(function(data) {
    console.log(data);
});

2.Promise 생성자 특징

  • new라는 클래스 생성자를 통해 Promise를 만드는 순간 전달한 resolve, reject 콜백함수가 바로 실행된다.
  • 만약 사용자가 요구했을 때만 네트워크 요청을 하는 경우, 사용자가 요청하지도 않았는데 네트워크요청이 될 것이기 때문에 주의해야 한다.
let promise = new Promise(function(resolve, reject) {
  console.log(1);

setTimeout( function() {
  resolve(2);
}, 1000)l;
});

promise.then(function(data) {
  console.log(data);
});

3. Promise는 인터넷 익스플로러에서 사용할 수 없다

  • 이러한 이유로 순수 자바스크립트에서 개발을 해야할 때 바벨을 이용하면 좋다
  • 바벨 : 고차언어를 컴퓨터가 이해할 수 있도록 ES5이하의 저차언어로 변화시켜준다.

Promise 연습문제

1번 문제

function job() {
    return new Promise(function(resolve, reject) {
        reject();
    });
}

job()
	.then(function() {
    console.log(1);
	})
	.then(function() {
    console.log(2);
	})
	.then(function() {
    console.log(3);
	})
	.catch(function() {
    console.log(4);
	})
	.then(function() {
    console.log(5);
	});

/* 실행결과 
4
5
*/

promise에서 reject를 호출했기 때문에 맨 처음 콘솔에 4가 찍히고,
한 편으로는 catch 메서드에서 promise를 리턴하기 때문에 아래의 then도 같이 실행되기 때문에 콘솔에 5도 찍힌다

2번 문제

function job(state) {
    return new Promise(function(resolve, reject) {
        if (state) {
            resolve('success');
        } else {
            reject('error');
        }
    });
}

job(true)
	.then(function(data) {. // 첫 번째 then
    console.log(data);

    return job(false);
	})
	.catch(function(error) {  // 두 번째 catch
    console.log(error);

    return 'Error caught';
	})
	.then(function(data) {. // 세 번째 then
    console.log(data);

    return job(true);
	})
	.catch(function(error) { // 네 번째 catch
    console.log(error);
	});
  1. 처음에 promise에서 state=true이기 때문에 resolve를 호출해서 첫 번째 then 콜백함수가 실행된다

    • 이 때 콘솔에 success가 찍히고
    • job(false)라는 promise를 반환하기 때문에 다시 promise를 실행하는데
    • 이번에는 state = false 이기 때문에 reject(‘error’)가 호출된다
  2. reject(‘error’)가 호출되면 두 번째 catch의 콜백함수가 실행된다

    • 콘솔에 error가 찍히고
    • ‘Error caught’가 return값으로 나온다
    • 일반값이 반환되기 때문에 그 다음에 나오는 then의 콜백함수의 매개변수가 ‘Error caught’로 실행된다
  3. 이전에 일반값이 리턴되었기 때문에 세 번째 then의 콜백함수의 매개변수 data = ‘Error caught’ 가 들어가게 된다

    • 콘솔에 ‘Error caught’ 가 찍히고
    • job(true)로, promise가 리턴되기는 하지만 이후에 state=true 이기 때문에 실행할 then이 없어서 더 이상 chaining되지 않는다

3번 문제 : 콜백지옥 → Promise 변환

콜백지옥

class UserStorage {
  loginUser(id, password, onSucess, onError) {
    setTimeout(()=>{
      if (
        (id === 'ellie' && password === 'dream') ||
        (id === 'coder' && password === 'academy')
      ) {
        onSucess(id);
      } else {
        onError(new Error('not found'));
      }
    }, 2000)
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(()=> {
      if (user === 'ellie') {
        onSuccess({ name: 'ellie', role: 'admin' });
      } else {
        onError(new Error('no access'));
      }
    }, 1000);
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = promt('enter ypur password');
userStorage.loginUser
(id, 
password, 
user => {
  userStorage.getGRole(
    user, 
    userWithRole => {
      alert(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role.`)
    },
    error => { console.log(error) })
},
error => {
  console.log(error)
  }
);

Promise 변환

class UserStorage {
  loginUser(id, password) {
    return new Promise(resolve, reject) => {
      setTimeout(()=>{
        if (
          (id === 'ellie' && password === 'dream') ||
          (id === 'coder' && password === 'academy')
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 2000);
    }
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(()=> {
        if (user === 'ellie') {
          onSuccess({ name: 'ellie', role: 'admin' });
        } else {
          onError(new Error('no access'));
        }
      }, 1000);
    })
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = promt('enter ypur password');

userStorage
  .loginUser(id, password)
  .then(userStorage.getRoles)
  .then(user => alert(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role.`)
  .catch(console.log);
profile
새로운 것을 도전하고 노력한다

0개의 댓글