[javascript] 비동기 : Promise

Hyebin·2021년 4월 27일
0

Javascript

목록 보기
24/27
post-thumbnail

비동기 호출 포스팅에서 callback함수로 순차적 비동기 호출을 구현 할 때 콜백 지옥이란 문제점이 존재하며, 콜백 지옥을 개선하기 위한 방식이 있다고 했었다.

Promise가 콜백 지옥을 타파하고 비동기 작업을 하기위해 ES6부터 도입된 방식이다.

callback hell이 뭘까?

callback hell은 비동기 처리시 함수의 파라미터로 넘겨지는 콜백함수가 반복되어 코드 들여쓰기가 깊어지는 것을 말한다.

  • 단점 : 코드의 가시성이 매우 떨어진다.

아래 코드로 A, B, C, D를 출력한다면 실행시마다 랜덤으로 출력될 것이다.

let printString = string => {
    setTimeout( () =>  console.log(string) , Math.floor(Math.random() * 1000)+1)
}

let printAll = () => {
    printString('A')
    printString('B')
    printString('C')
  	printString('D')
}
printAll();

callback함수를 이용해 A, B, C, D를 순차적으로 실행하도록 만들어 준다면 아래처럼 쓸 수 있다.

let printString = (string, callback) => {
    setTimeout( () =>  { 
        console.log(string) 
        callback();
    }, Math.floor(Math.random() * 1000)+1)
}

let printAll = () => {
    printString('A', () => {
        printString('B', () => {
            printString('C', () => {
            	printString('D', () => {}
            });
        });
    });  
}
printAll();

처리해야할 데이터가 4개임에도 코드의 들여쓰기가 깊어지고 가시성이 떨어진다. 만약 처리해야할 데이터가 수 십개라면 이야말로 콜백 지옥이 되버린다.

Promise 객체

앞 서 언급했던 것과 같이, Promise는 callback함수의 대안으로 등장했으며 비동기 처리를 위한 객체이다.
callback을 인자로 주지 않고, return 값을 새로운 promise 객체로 준다.

new Promise(resolve, reject => {});

executor(실행 함수)로 들어오는 resolve와 reject는 자체적으로 제공되는 콜백함수이다.

  • resolve
    주어진 값을 이행하는 promise 객체를 반환 (성공적 처리상태)
  • reject
    거부하는 promise 객체를 반환 (에러 처리)

Promise의 상태

promise 객체에는 3가지 상태가 존재한다.

  • 대기(pending): 초기 상태
    (= 비동기 처리 로직이 완료 전 상태)
  • 이행(fulfilled): 비동기 처리가 완료되어 promise가 결과 값을 반환 상태
  • 거부(rejected): 비동기 처리 실패 또는 오류 발생 상태

Promise.prototype 메서드

1) then()

  • resolve를 실행하면 fulfilled 상태가 되고, 이때 then() 메서드를 이용해 처리 결과값을 받을 수 있다.

case1. 함수가 값을 반환하지 않을 때, undefined를 결과값으로 갖는다.

let printFunc = () => {
  return new Promise((resolve, reject) => {
    resolve()
  });
}

printFunc().then((value) => {
  console.log(value)
}); //undefined

resolve에 인자를 담아가더라도 return을 하지 않으면 undefined를 갖는다.

let printFunc = () => {
  return new Promise((resolve, reject) => {
    resolve('Hello')
  });
}

printFunc().then((value) => {
  console.log(value)
}); //Hello

case2. 함수가 값을 반환할 때 리턴값을 결과값으로 갖는다.

printFunc().then((value) => {
  console.log(value)
  return value;
}); 

2) catch()

  • reject를 호출하면 rejected 상태가 되고, 실패 처리의 결과값(실패 이유)를 catch() 메서드로 받을 수 있다.
let failFunc = () => {
  return new Promise((resolve, reject) => {
    reject(new Error('It is failed'))
  })
}

failFunc().then().catch(err => {
  console.log(err)
})

3) finally()

  • 성공, 실패(에러) 상관없이 Promise가 처리 된 후에 지정된 콜백 함수가 무조건 한 번은 실행된다.

Promise chaining

여러개의 promise들을 연결해서 사용하는 것을 말한다.

예를 살펴보자.
함수마다 Promise 객체를 생성해서 setTimeout()으로 1초 후에 resolve()를 호출해주었다.

function gotoCode() {
  return new Promise((resolve, reject) => {
 	setTimeout(() => {resolve('1. go to code')},1000)
  });
}
function sitAndCode() {
  return new Promise((resolve, reject) => {
 	setTimeout(() => {resolve('2. sit and code')},1000)
  });
}
function eatLunch() {
  return new Promise((resolve, reject) => {
 	setTimeout(() => {resolve('3. eat lunch')},1000)
  });
}
function gotoBed() {
  return new Promise((resolve, reject) => {
 	setTimeout(() => {resolve('4. go to Bed')},1000)
  });  
}

resolve()를 부르게 되면 대기 상태에서 이행 상태로 넘어오며 첫번째 then()이 실행되고, 반환값을 받아서 두번째 then() 인자로 넘겨준다.
두번째 then()에서도 마찬가지로 반환값을 받아서 세번째 then()의 인자로 넘겨주는 체이닝 형태가 된다.

gotoCode()
  .then(data => {
    console.log(data)
    return sitAndCode()
  }) 
  .then(data => {
    console.log(data)
    return eatLunch()
  })
  .then(data => {
    console.log(data)    
    return gotoBed()
  }) 
  .then(data => {
    console.log(data)
  })

1초마다 출력된다.

Promise.all()

여러 개의 promise를 처리할 때 사용된다.

Promise.all(iterable)

promise.all의 인자는 Array와 같이 iterable한 객체가 오며, 인자 배열의 요소로 Promise가 들어온다.

  • 쓸 때 주의할 점!
    주어진 promise 중 오류가 발생하면 다른 promise 처리하지 않고, 오류를 반환한다.
const getDataFromFile = filePath => {
  return new Promise((resolve, reject) => { 
      fs.readFile(filePath, 'utf-8', (err, data) => {
        if(data) { 
          resolve(data.toString());  
        }
        reject(err); //reject에는 에러메세지를 
      });    
  });
};

const readAllUsers = () => {
  return Promise.all([getDataFromFilePromise(user1Path), getDataFromFilePromise(user2Path)]) //배열을 준거고 배열의 요소가 promise이다. 
  .then((datas) => {
    return datas.map(el => JSON.parse(el)); 
  })

막간 퀴즈

  • Promise 실행함수가 가지고 있는 두 개의 파라미터, resolve, reject는 각각 무엇을 의미하나요?
    resolve : 비동기 처리가 완료될 때 결과값
    reject : 비동기 처리가 실패하거나 오류 날 때 결과값

  • resolve, reject함수에는 인자를 넘길 수 있습니다. 이때 넘기는 인자는 어떻게 사용할 수 있나요?
    .then, .catch의 인자로 들어가 사용한다.

  • new Promise()를 통해 생성한 Promise 인스턴스에는 어떤 메소드가 존재하나요? 각각은 어떤 용도인가요?
    .catch() : 오류나 에러가 났을 때 실행될 메서드
    .then() : 정상적으로 작동 됐을 때 실행될 메서드
    .finally() : 충족되거나 거부되는지 여부에 관계없이 지정된 콜백 함수가 실행 메서드
  • Promise.prototype.then 메소드는 무엇을 리턴하나요? Promise를 리턴

  • Promise.prototype.catch 메소드는 무엇을 리턴하나요?
    promise를 리턴

  • Promise의 세가지 상태는 각각 무엇이며, 어떤 의미를 가지나요?
    Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
    Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
    Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

0개의 댓글