JavaScript(Promise/async/await)

박정호·2022년 4월 4일
0

JS

목록 보기
13/24
post-thumbnail
post-custom-banner

promise

프로미스는 비동기 함수를 동기 처리하기 위해 고안한 객체이고, 비동기 작업이 완료된 이후에는 다음 작업을 연결시켜 진행.
주로 서버에서 받아온 데이터를 화면에 표시할 때 사용.

예를 들어 웹애플리케이션을 구현할 때 서버에서 데이터를 요청하고 받아오기 위해 아래와 같은 API를 사용한다.

$.get('url 주소/products/1', function(response) {
  // ...
});

API가 실행되면 서버에 ‘데이터 하나 보내주세요’ 라는 요청을 보낸다. 그런데 여기서 데이터를 받아오기도 전에 마치 데이터를 다 받아온 것 마냥 화면에 데이터를 표시하려고 하면 오류가 발생하거나 빈 화면이 뜬다. 이와 같은 문제점을 해결하기 위한 방법 중 하나가 프로미스이다.

promise 예제

setTimeout함수는 비동기식 방식으로 처리되는 함수

function goToSchool() {
    console.log("학교에 갑니다.");
}
function arriveAtSchool() {
    setTimeout(function() {
        console.log("학교에 도착했습니다.");
    }, 1000);
}
function study() {
    console.log("열심히 공부를 합니다.");
}
goToSchool();
arriveAtSchool();
study();

따라서, study()가 arriveAtSchool보다 우선 실행이 된다.

학교에 갑니다.
열심히 공부를 합니다.
학교에 도착했습니다.

위와 같은 코드를 동기식으로 출력하고 싶다면?

콜백함수 방식

  • 처음으로 출력되야할 함수를 호출하고, 인자값으로 그 다음 출력되야할 함수를 설정한다. 따라서, 함수내에서 함수가 호출되고, 또 다시 그 인자값에 다음 출력되야할 함수를 설정한다.
  • 만약 수많은 코드들을 이런 식으로 출력하게 되면, 콜백지옥이 탄생한다.
function goToSchool(arriveParameter){
    console.log("학교에 갑니다.");
    arriveParameter(study);
}
function arriveAtSchool_asis(studyParameter) {
    setTimeout(function() {
        console.log("학교에 도착했습니다.");
        studyParameter();
    }, 1000);    
}
function study() {
    console.log("열심히 공부를 합니다.");
}
goToSchool(arriveAtSchool_asis);

promise 방식

  • Promise객체의 파라미터에 콜백함수를 대입하고 그 안에 arriveAtSchool함수를 넣어준다. 그리고 콜백함수의 첫번째 파라미터값을 실행하게 하면, arriveAtschool함수가 실행된다. 그리고 then함수에 의해 arriveAtSchool가 성공적으로 실행될 시 수행할 함수에 study()함수를 주어 차례로 출력하게 한다. 즉, arriveAtSchool함수의 실행 성공여부에 따라서 다음코드가 결정되기 때문에 비동기식 출력을 해결할 수 있게 된다.
  • 직접 콜백함수를 작성하는 것과 의미상으로 큰 차이는 없지만, 직관적이고 이해하기 쉽게 보여주는 일종의 디자인 패턴.
function goToSchool() {
    console.log("학교에 갑니다.");
}
function study() {
    console.log("열심히 공부를 합니다.");
}
function arriveAtSchool() {
    return new Promise(function(resolve){
        setTimeout(function() {
            console.log("학교에 도착했습니다.");
            resolve();
        }, 1000);
    });
}
goToSchool();
arriveAtSchool().then(function(){
    study();
});

참고:https://sangminem.tistory.com/284

참고: 캡틴판교

Promise는 성공,실패 판정기계

성공하면 then(), 실패하면 catch()가 실행된다.

기본 구조

var 프로미스 = new Promise(function(성공, 실패){ //성공 = resolve, 실패 = reject
  성공();
});
프로미스.then(function(){
}).catch(function(){
});

Promise 내의 결과값을 전달 가능

var 프로미스 = new Promise(function(성공, 실패){
  var 어려운연산 = 1 + 1;
  성공(어려운연산); //성공함수가 실행되어, then함수의 파라미터값으로 결과값이 전달
});
프로미스.then(function(결과){
  console.log('연산이 성공했습니다' + 결과)
}).catch(function(){
  console.log('실패했습니다')
});

Promise 3가지 states

Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태

Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태

Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

Promise 흐름도


출처: MDN

Promise 응용

resolve, reject기능을 알 수 있었고, 조건을 주어 상황에 맞는 코드 실행이 가능.

function goToSchool() {
    console.log("학교에 갑니다.");
}
function cure() {
    console.log("양호실에 가서 약을 발랐습니다.");
}
function study() {
    console.log("열심히 공부를 합니다.");
}
function arriveAtSchool(status) {
    return new Promise(function(resolve, reject){
        setTimeout(function() {   
            if(status === 1) {
                resolve("학교에 도착했습니다.");
            } else {
                reject("중간에 넘어져 다쳤습니다.");
            }
        }, 1000);
    });
}
goToSchool();
arriveAtSchool(2)
.then(function(res){
    console.log(res);
    study();
})
.catch(function(err){
    console.log(err);
    cure();
});

상황 1.
1. goToSchool() 정상실행
2. arriveAtSchool()함수를 실행
3. 인자로 1을 전달하면 함수내부의 조건에 의해 resolve함수 실행
4. 이때 함수내부의 인자값으로 '학교에 도착했습니다'가 then함수 내부의 콜백함수의 파라미터값으로 전달.
5. 그리고 파라미터값 출력 후 study() 실행

학교에 갑니다.
학교에 도착했습니다.
열심히 공부를 합니다.

상황2.
1. goToSchool() 정상실행
2. arriveAtSchool()함수를 실행
3. 인자로 1을 제외한 다른 값을 전달하면 함수내부의 조건에 의해 reject함수 실행
4. 이때 함수내부의 인자값으로 '중간에 넘어져 다쳤습니다.'가 catch함수 내부의 콜백함수의 파라미터값으로 전달.
5. 그리고 파라미터값 출력 후 cure() 실행

학교에 갑니다.
중간에 넘어져 다쳤습니다.
양호실에 가서 약을 발랐습니다.

참고 : https://sangminem.tistory.com/284

Promise & Ajax

만약 서버에서 GET요청을 해서 성공했는지를 파악해보자.

  1. 상단에 jQuery Ajax CDN 파일 설정
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> 
  1. 두가지 Ajax 요청 방식
$.ajax({
  type : 'GET',
  url : 'URL 경로'
})
------------------
$.get('URL 경로')
  1. 실행여부에 따른 실행코드
  • .done은 서버 통신 성공시 콜백 처리
  • .fail은 서버 통신 실패시 콜백 처리
$.ajax({
  type : 'GET',
  url : 'URL 경로'
}).done(function(결과){
  console.log(결과);
});
-----------------------------------
$.get('URL 경로').done(function(결과){
  console.log(결과)
});
  1. Promise안에 Ajax요청 코드를 작성하고 서버 통신 성공시 resolve함수가, 서버 통신 실패 시 reject함수가 실행된다. 성공시에는 resolve함수에 인자값으로 서버에서 받아온 데이터값이 전달되고, then의 콜백함수에서 그 값을 출력한다.
var 프로미스 = new Promise(function(resolve, reject){
        $.get({
        type: "GET",
        url: "https://codingapple1.github.io/hello.txt "
    }).done(function(result){
        resolve(result);
    }).fail(function(){
        reject();
    })
    });
프로미스.then(function(result) {
  console.log(result);
}).catch(function(){
    console.log("실패");
})

Promise Chaining

만약 위의 Ajax요청을 여러번 한다면?

직관적으로는 다음과 같이 코드를 작성하면 된다.(.then안에 새로운 Promise생성시 뒤에 .then을 이어 쓸 수 있다는 것을 알고 있자.)

프로미스.then(()=>{둘째실행할거}).then(()=>{셋째실행할거})
  1. then의 콜백함수에서 첫번째 요청에 대한 데이터값이 출력되고, 그 다음 요청에 대한 Promise를 생성한다.( = 아래의 코드에서 표시된 부분에 대한 새로운 then값이 생성)

  2. 성공시 resolve함수에 result2값이 인자로 전달되어, 다음에 생성된 then 함수내에서 출력할 수 있게 된다.

 프로미스.then(function (result) {
        console.log(result);
        var 프로미스2 = new Promise(function (resolve, reject) {
            $.get({
                type: "GET",
                url: "https://codingapple1.github.io/hello2.txt "
            }).done(function (result2) {
                resolve(result2);
            }).fail(function () {
                reject();
            })
        });
        return 프로미스2
    }).then(function (result2) {
        console.log(result2);
    }).catch(function () {
        console.log("실패");
    })
  1. 아래의 코드처럼 같은 형식의 promise라는 것을 알 수 있고, 깔끔하게 함수로 만들어서 반환하자.
  프로미스.then(function (result) {
        console.log(result);
        return 프로미스2(); //따로 만들어진 프로미스2 함수 리턴
    }).then(function (result2) {
        console.log(result2);
    }).catch(function () {
        console.log("실패");
    })
    function 서버통신함수(){ 함수의 반환값으로 promise 생성
        return new Promise(function (resolve, reject) {
            $.get({
                type: "GET",
                url: "https://codingapple1.github.io/hello2.txt "
            }).done(function (result2) {
                resolve(result2);
            }).fail(function () {
                reject();
            })
        });
    }
  1. 위의 코드의 경우 hello2에 대한 요청방식이고, URL을 파라미터값으로 만들어서 유동적으로 주소를 넣어줄 수 있게 설정
  프로미스.then(function (result) {
        console.log(result);
        return 서버통신함수("https://codingapple1.github.io/hello2.txt "); 
        //새로운 요청마다 주소값만 서버통신함수의 인자값으로 주면 끝
    }).then(function (result2) {
        console.log(result2);
    }).catch(function () {
        console.log("실패");
    })
    function 서버통신함수(URL){ // 주소값을 URL 파라미터에 저장
        return new Promise(function (resolve, reject) {
            $.get({
                type: "GET",
                url: URL // get에 URL 지정
            }).done(function (result2) {
                resolve(result2);
            }).fail(function () {
                reject();
            })
        });
    }

async

함수 앞에 async를 작성함으로써 그 함수가 Promise 성질을 갖게 된다.

일반적인 promise 형식

var 프로미스 = new Promise(function(resolve, reject){
	실행코드
    resolve();
})
프로미스.then(function(성공시 실행할 코드)).catch(function(실패시 실행할 코드));

async 형식

async function 함수(){
	return 실행코드;
}
함수().then(function(성공실행코드));

단 실행이 성공되었을 때의 코드만 반환된다. 만약 강제로 실패코드를 반환하고 싶다면 다음과 같이 작성 가능.

async function 함수(){
	return Promise.reject('실패'); // 에러 출력
}

await

  • async키워드를 쓴 함수 안에서만 사용가능
  • .then()과 같은 역할을 해주는 키워드
async function 더하기(){
  var 어려운연산 = new Promise((resolve, reject)=>{
    var result = 1 + 1;
    resolve();
  });
  var 결과 = await 어려운연산; // 어려운연산이라는 프로미스가 실행완료될떄까지 기다리라는 뜻
   console.log(result); // 2 
}
더하기();

만약 프로미스가 실패할 경우 await의 경우 코드에러가 나고, 실행이 멈추게 되고, 그 뒤로의 코드들은 실행되지 않게 된다.
따라서, 에외처리 문법인 try~catch문을 사용한다.
try 속 코드가 에러가 난다면 catch 내부의 코드를 실행해달라는 조건을 거는 것이다.

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    실패();
  });
  try {  var 결과 = await 어려운연산 }
  catch { 어려운연산 Promise가 실패할 경우 실행할 코드 }
}

async, await 응용

버튼 클릭시 성공판정 promise 만들기.

await를 .then을 대신하여 사용해보고, await는 async 내부에서만 사용가능하므로 async 또한 사용해보자.

  • new Promise + await 방식
<button id="test">버튼</button>
<script>
  var 프로미스 = new Promise(function(성공, 실패){
      document.getElementById('test').addEventListener('click', function(){
        성공();
      });
  })
  async function 버튼누르기(){
    var 결과 = await 프로미스;
    console.log('성공했으요')
  }
  버튼누르기();
</script>
  • async + await 방식
<button id="test">버튼</button>
<script>
  async function 프로미스(){
    document.getElementById('test').addEventListener('click', function(){
      return '성공했어요'
    });
  }
  async function 버튼누르기(){
    var 결과 = await 프로미스();
    console.log(결과)
  }
  버튼누르기();
</script>
profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)
post-custom-banner

0개의 댓글