본문 바로가기
JavaScript

Preparation before Using "Promise"

by oncerun 2021. 10. 12.
반응형

Promise를 사용하고 고전적인 콜백 함수를 통한 비동기 네트워크 통신을 개선하기 전, promise의 개념을 다시 한번 잡고,  코드를 리팩터링 하기 위해 MDN 방문.

 

Promise를  주요 주제로 잡고 공부를 진행할 것이나 중간 중간 생소한 문장과, 단어에 하이퍼링크가 걸려있는 건 고추참치.. 

 

 

Promise 객체는 비동기 작업이 맞이할 미래의 완료 혹은 실패를 의미한다. 

 

기본적으로 promise는 함수에 콜백을 전달하는 대신에, 콜백을 첨부하는 방식의 객체이다.  음.. 보통 비동기함수에서 성공 시 익명 함수 인자로  return값을 받아서 처리하는 데, 이 모델이 아니라 promise객체 내부에 콜백 함수를 첨부한 후 promise객체 자체를  return 하는 개념인 것 같다.

"그러면 어떤 이점이 존재할까?" 라는 의문은 뒤로 밀어 두고 Promise의 작동 방식과 Promise의 사용방법을 보자

만약 asyncFunc()이 비동기 함수라면

const promise = asyncFunc(arg);
promise.then(sucCallback, failCallback);

(내가 promise에 대한 이해가 부족하다고 생각하여 await와 async 오퍼레이터는 생략했다. )

 

현대의 함수는 함수인자에 callback func를 전달하지 않고, callback을 붙여서 사용할 수 있게 promise를 반환한다.

 

기존의 콜백 지옥이라고 불렸던 코드

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

 

Guaranttees 

 

콜백함수를 전달하는 고전적인 방식과 다르게 promise를 반환하면 다음과 같은 특징을 보장한다.

 

  • 콜백은 자바스크립트의 Event Loop가 현재 실행중인 콜 스택을 완료하기 이전에는 절대 호출되지 않는다.
  • 비동기 작업이 실행된 이후 then()을 이용해 추가한 콜백 함수도 위와 같은 특징을 같는다.
  • 즉 각각의 콜백함수는 순서가 보장된다.

 

현재 실행중인 콜 스택을 완료

 

해당 문장을 자세히 보자. 어떻게 보장할 수 있다는 것일까?

 

동시성 모델과 이벤트 루프

 - 자바스크립트는 코드 실행, 이벤트 수집과 처리, 큐에 놓인 하위 작업들을 담당하는 이벤트 루프에 기반한, 동시성 모델을 가지고 있다.

다음 그림은 자바스크립트의 런타임 개념을 볼 수 있는 사진이다.

 

1) stack : 함수의 호출은 Frame들의 스펙을 형성한다.

2) Heap : 객체들은 힙 안에 할당된다. 힙은 구조화되지 않은 넓은 메모리 영역을 지칭한다.

3) Queue : 자바스크립트 런타임은 처리할 메시지 목록인 메시지 대기열을 사용한다. 각 메시지에는 메시지를 처리하기 위해 호출되는 관련 함수가 존재한다.

Event Loop 중 어떤 시점에서 런타임은 대기열에서 가장 오래된 메시지부터 처리한다.  (큐 자료구조) 메시지는 Queue에서 제거되고, 해당기능의 메시지를 입력 매개변수로 호출한다.  이후 그 메시지를 처리하기 위한 함수를 위한 새로운 stack에 frame이 생성된다.

 

* 함수의 처리는 스택이 다시 비워질 때까지 계속된다. 이후 이벤트 루프는 큐의 다음메시지를 처리하고 다음 메시지가 없을 경우 메시지를 수신하기 위해 대기상태가 된다.

 

Run - to - completion

- 각 메시지는 다른 메시지가 처리되기 전에 완벽하게 처리된다.  또한 실행되는 함수가 thread에서 실행된다면, 런타임 시스템에서 다른 thread에서 다른 코드를 실행하기 위해 특정 시점에 멈출 수 있다. ( js는 메인 스레드가 싱글이지만 동시성을 위해 서브 스레드로 분기할 수 있으니까?)

 

그런데 만약 메시지가 stack에 올려졌을 때 처리할 양이많으면, 웹 애플리케이션은 사용자의 인터랙션을 처리할 수 없을 것이다. 추천하는 방법은, 메시지 처리를 짧게 만들거나, 하나의 메시지를 여러 개의 메시지로 나누는 방법 등이 있다.

 

다양한 런타임 중 통신

 

워커, iframe은 자신만의 스택, 힙, 메시지 큐를 지니고 있어, postMessage method를 통해서만 서로 통신할 수 있다. 이 메서드는 다른 런타임이 message 이벤트 핸들러를 등록하고 있다면, 해당 런타임의 큐에 메시지를 추가할 수 있다. (오!)

 

 1) window.postMessage() : 동일 출처 정책을 우회하여, 안전하게 데이터를 전송할 수있다 이벤트 리스너의 message를 추가하여, origin, source의  검사를 통해 서로 신뢰할 수 있는 origin인지 확인하여 데이터 주고받을 수 있다. 사용방법은 예제를 참고하면 쉽게 알 수 있다.

 

 

Promise chain

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

doSomething()이라는 함수가 Promise 객체를 반환해야 가능하다. 이 말은 반환 값이 꼭 존재해야 해당 값을 promise객체로 감싸서 전달 전달을 할 수 있다는 것이다.  또한 각 비동기 함수의 오류를 cath() 구 문 한 번으로 잡은 것도 기존과 별개이다. 기존에는 각 비동기 함수에서 각 에러를 처리했던 점과 달리 한 번만 사용한다는 점이다.

 

 

그럼 Promise 객체는 어떻게 디자인 됬을지 궁금하다.

 

Promise는 pending, fulfilled, rejected 3가지 상태를 가진다. 

pending은 초기상태를 의미하고, fulfilled는 연산이 성공적으로 완료된 경우, rejected는 연산이 실패하는 경우이다.

 

 

 

다음과 같은 그림을 보면 Promise는  2가지 조건 (fulfill, reject)에 따라 분기되며, then()을 통해 처리된다. 또한 return 값 또한 Promise객체인 것을 확인할 수 있으며 이후 chaining을 통하여, 행위를 계속할 수 있다.

 

 

Promise 객체를 알아보자.

 

생성자

- Promise의 생성자는 주로 프로미스를 지원하지 않는 함수를 감쌀 때 사용한다고 나와있습니다.

프로미스를 지원하지 않는? 즉 프로미스를 반환하지 않는 함수를 감싸서 사용하면, Promise를 반환하게 한다라는 말?

 

new Promise(executor)
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('foo');
  }, 300);
});

promise1.then((value) => {
  console.log(value);
  // expected output: "foo"
});

console.log(promise1);
// expected output: [object Promise]

executor의 매개변수는 resolve, reject 인수를 전달할 실행 함수이며, 전달된 실행 함수는 프로미스 구현에 의해 resolve와 reject 함수를 받아 즉시 실행된다.

resolve 및 reject 함수는 호출할 때 각각 프로미스를 이행하거나 거부합니다. 실행 함수는 보통 어떤 비동기 작업을 시작한 후 모든 작업을 끝내면 resolve를 호출해 프로미스를 이행하고, 오류가 발생한 경우 reject를 호출해 거부합니다.

 

 

실행 함수는 매개 변수로 두 가지 함수를 받아야 하는데, 첫 번째 인자인 resolve함수는 비동기 작업을 성공적으로 완료해 결과를 값으로 반환할 때 호출해야 하고 -> 예제를 참고하면, resovle의 매개변수에 결괏값을 넣어주는 것 같다.

두 번째 함수 reject()는 작업이 실패하여 오류의 원인을 반환할 때 호출, 두 번째 함수는 주로 오류 객체를 받는다.

 

함수에 프로미스 기능을 추가하려면 단순히 프로미스를 반환하게 하면 된다.

 

function getData(url) {
//이 함수는 promise를 반환하도록 구현할 것
// fetch는 기본적으로 promise를 반환하기 때문에 xhr을 사용해 연습
	return new Promise( (resolve, reject) => {
    		
            const xhr = new XMLHttpRequest();
            xhr.open('GET', url);
            xhr.onload = () => resolve(xhr.responseText);
            xhr.onerror = () => reject(xhr.statusText);
            xhr.send();
    
    });
}

 

 

프로미스의 메서드는 4개

 

1. Promise.all(iterable)

2. Promise.race(iterable)

3. Promise.reject()

4. Promise.resolve()

 

 

Promise를 생성하여 사용하는 방식은 크게 두가지로 나뉘어 지는 것 같다.

 

익명 함수내부에서 Promise 객체를 반환하는 것과 변수에 Promise객체를 생성해서 넣는 방법이다.

어떠한 차이가 존재할까?

var promise = function(param){

	return new Promise( (resolve, reject) = > {
    
    	//비동기를 수행한후
        
        if(true){
        	//성공적으로 맞췄다면
            resolve("Success");
        }else{
        	//다양한 이유로 실패했다면
            reject("문자열을 줘도되고, 객체를줘도되고...");
        
        }

    });
};

var _promise = new Promise((resolve, reject) => {

	//비동기를 수행한후
        
        if(true){
        	//성공적으로 맞췄다면
            resolve("Success");
        }else{
        	//다양한 이유로 실패했다면
            reject("문자열을 줘도되고, 객체를줘도되고...");
        
        }
});

 

 

 - Promise 객체를 만드는 행위와 동시에 Promise내부의 함수가 실행된다. 하지만 익명함수를 변수에 저장하는 것은 원하는 시점에 사용할 수 있다. 

 - 즉시 실행되기 때문에 _promise.then().catch() 등으로 활용될 수 있으며, 한번 수행된 값이 저장되기 때문에, 반복해서 해당 코드를 작성해도, 동일한 값만 리턴할 것이다.

 

 

await, async

 

 ECMAScript 2017에서 추가 된 async/await 구문(Syntactic sugar)에서 비동기 코드의 장점을 확인할 수 있습니다. 

 

위에서 아래로, java를 공부하면서 내가 작성한 코드가 위에서 아래로 흐르는 그림에 익숙해져있기에, 그 코드작성은 나에게 익숙하면서, 당연하다고 느꼇다. Promise를 통해 비동기를 개선함에 있어서도 작성하는 코드의 양 혹은 return값이 전부 Promise 객체여야 한다는점 등등이 내게 Promise를 실행하는 부분은 좋은데 정의부분은 조금 지저분하다 라고 생각하게 되었는데, 그래서 await, async를 사용하고 싶었지만, 해당 프로젝트는 트랜스컴파일러를 사용하지 않았기 때문에 그냥 쓰고싶지만 참고, 수많은 콜백으로 도배했던 기억이 난다.  하지만 이번에는 타입스크립트를 사용하며, 타입스크립트의 설정에는 멋진 바벨기능이 포함되어 있기에 마구마구 사용할 것이다. 

 

사용하기 전 Promise의 개념을 확인했으니, await, async를 사용해보자!  + (fetch API)  = ^.^;

 

 

 

 

 

 

 

 

 

 

 

 

반응형

'JavaScript' 카테고리의 다른 글

Fetch API Response  (0) 2021.10.16
Fetch API Request  (0) 2021.10.16
인스턴스 생성  (0) 2021.08.02
[ES8] async, await  (0) 2020.07.03
[ES6] Promise  (0) 2020.07.02

댓글