Developer/JavaScript

자바스크립트 , promise

단님 2024. 6. 7. 00:09
728x90

자바스크립트에서 가장 큰 목적은 무엇이였을까 ?

html에서 구조를 , CSS에서 모양을 만들었다면,

자바스크립트를 통해 동적인 액션을 구현한다는 것이다.

 

이 동적인 액션들이 내가 원할때 실행되어야하고 , 원하지 않을때 실행되지 말아야하며

연산을 하는 도중에 값이 도출될 수 없듯 

일에 순서와 절차를 만들어 줘야한다.

 

하지만 자바스크립트는 현재 어떻게 움직이고 있을까 ?

상단부터 하단으로 순차적으로 읽어 들어가지만,

여러가지의 함수나 실행이 어느 한 로드가 틀어지게 된다면 실행이 얽힐 수 있을것이다.

 

예를들어 여러개의 setTimeout 함수를 실행시킬때

전부다 1초의 시간을 준다면 어떻게 됬을까 ?

로드에 맞춰 실행 시작하면서 , 모든 함수가 로드 후 1초후에 실행이 마쳤다.

그 말은 즉, 함수의 실행 시점이 같다는 것으로 볼 수있는데 

이에 대한 시간을 조절하기 위해 for 문을 돌려 1초*i 를 했었다.

즉 자바스크립트의 자체적 실행 자체는 비동기적인 실행이라고 할 수 있겠다.

앞동작에 대한 대기를 하지 않기 때문이다.

 

예를 들어 웹브라우저에 네이버 주소를 검색하는 순간 우리가 다른 행동을 할 수 없는 상태가 된다.

요청을 하고, 주소값을 받아오는 시간을 대기한다.

즉슨, 요청과 요청에 대한 처리가 동기적으로 하나의 흐름에 따라 움직임을 알 수 있다.

동기적이라고 하는 것은 여러가지의 실행들을 순차적으로 이어나가는 것을 의미한다.

 

promise 가 이를 도와주는 함수이자 생성자 함수이자 객체이다.

 

  • Promise는 함수입니다:
    • JavaScript에서 Promise는 함수로 정의되어 있습니다. 즉, typeof Promise는 'function'입니다.
  • Promise는 생성자 함수입니다:
    • 생성자 함수는 new 키워드를 사용하여 새로운 객체를 생성할 수 있는 함수입니다. Promise는 생성자 함수로 사용되어 새로운 Promise 객체를 생성합니다.
  • Promise 객체:
    • Promise 생성자 함수를 사용하여 생성된 객체는 프로미스 객체입니다. 이 객체는 비동기 작업의 완료나 실패를 나타내는 값을 갖고 있습니다.

 

 -Promise는 클래스와 유사하게 동작하지만, 정확히 말하면 생성자 함수입니다.

따라서, Promise는 함수라고 하는 것이 정확합니다.

하지만 이 함수는 특별히 생성자 함수로 사용되어 프로미스 객체를 생성할 수 있습니다.

 

함수를 실행할거야 그런데 여기에 대해 성공하면 이것을 실패하면 이것을 해줘.

라는 실행을 이어나갈 수 있게 하나의 흐름을 만들어 주는것이다.

물론 promise 안에 promise 가 들어갈 수 있다 

또 성공하고 난 다음에 무엇을 할 지 조건부 ,여러가지의 콜백함수를 이어나갈 수도 있다.

형식은 다음과 같다.

 

 

new Promise((resolve, reject) => { // 매개변수의 활용.
  resolve('성공'); //(성공에 대한 , 실패에 대한) 둘중 하나만 실행.
  reject('실패'); // 상단이 성공 하단이 실패
  }).then( // 비동기적으로 콜백 함수 두개 실행. 코드들이 다 실행된 다음 실행(비동기).
    c=> console.log(c), //성공에 대한 콜백함수
    d=> console.log(d) // 실패에 대한 콜백함수
  );

new 생성자 함수를 통해 promise 함수를 실행하고 , 

성공에 대한 매개변수 , 실패에 대한 매개변수를 지정한다.

물론 이것은 매개변수임으로 다른 이름으로 해도 오류가 없겠다만 ,

개인적으로 resolve, reject 라고 명시하는것이 가독성이 좋다고 느껴진다.

첫번째 인자로 성공에 대한 변수 , 두번째 인자로 실패에 대한 변수를 쓰는것이다.

그 변수를 받아 resolve, reject 에 대한 값을 () 를 통해 넣어 전송하게 된다.

resolve(성공) 에 대한 처리만 할 것이라면 reject 를 매개변수에 쓰지 않아도 되나

reject(실패)에 대한 처리만 할것이면 resolve, reject  모두 명시해야한다.

매개변수의 위치에 따른 기능 때문이다.

 

than 은  앞에 코드들이 실행된후 실행되는데 , 

성공에 대한 실행콜백함수 와 실패에 대한 실행콜백함수가 들어갈 수 있다.

하지만 catch 메서드(실패시 실행콜백함수)를 통해

일반적으로 than은 성공에 대한 실행콜백함수를 명시하고,

catch 는 실패에 대한 실행 콜백 함수를 명시한다.

즉 than 에 콜백함수가 두개이상 된다면 , 마지막은 실패에 대한 함수가 될 수 있다는 점에 주의하자.

fainally 메서드 (무조건 실행하는 메서드)는 앞의 코드들이 모두 실행된 후

성공또는 실패에 무관하게 무조건 실행하게 하는 메서드이다.

간단한 예시를 보겠다.

myPromise
.then((value) => { // 성공적으로 수행했을 때 실행될 코드
console.log("Data: ", value); // 위에서 return resolve(data)의 data값이 출력된다
})
.catch((error) => { // 실패했을 때 실행될 코드
console.error(error); // 위에서 return reject("Error")의 "Error"가 출력된다
})
.finally(() => { // 성공하든 실패하든 무조건 실행될 코드


})

 

함수를 활용한 promise 활용

// 비동기 작업을 시뮬레이션하는 함수
function asyncTask() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = true; // 작업 성공 여부
            if (success) {
                resolve("작업이 성공적으로 완료되었습니다!");
            } else {
                reject("작업이 실패했습니다.");
            }
        }, 2000); // 2초 후에 완료
    });
}

// Promise 사용 예제
asyncTask()
    .then(result => {
        console.log(result); // 작업이 성공했을 때 실행
    })
    .catch(error => {
        console.error(error); // 작업이 실패했을 때 실행
    });

 

promise 의 체이닝
나는 이렇게 말할래 then 지옥
new Promise(resolve => {
    resolve(1);
})
    .then(
        result => {
            console.log('첫 번째 프라미스');
            console.log(++result);

            return result; // 성공값으로 반환시킴.reslove(2);
        }
    )
    .then(
        result => {
            console.log(++result);

            return result; // 성공값으로 반환시킴 .resolve(3);
        }
    )
    .then(
        result => console.log(++result) //콘솔에 찍히게됨 .reslove(4);
    );

 

promise의 체이닝이란 프로미스의 실행을 연달아 then 을 통해  비동기 실행을 연결하여

하나의 실행 흐름을 만들어 내는 것을 의미한다.

then 메서드의 호출은 결과값을 리턴하면 해당 결과값을 가지는

기존 프라미스 객체를 다시 반환한다 , 체이닝 연산을통해 연속적인 결과 값 가공이 가능하다.

단, 연달아 사용할 경우 꼭 return 하여 기존 프라미스 객체 또는 값을 반환 시켜줄것.

 

-값을 return 할 경우

new Promise((resolve, reject) => {
  resolve(1);
})
.then(value => {
  console.log(value); // 1
  return value + 1;
})
.then(value => {
  console.log(value); // 2
  return value + 1;
})
.then(value => {
  console.log(value); // 3
});

 

-기존 promise 를 return 할 경우

function fetchData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url) {
        resolve(`Data from ${url}`);
      } else {
        reject('URL is required');
      }
    }, 1000);
  });
}

fetchData('https://api.example.com/data1')
  .then(data1 => {
    console.log(data1); // Data from https://api.example.com/data1
    return fetchData('https://api.example.com/data2');
  })
  .then(data2 => {
    console.log(data2); // Data from https://api.example.com/data2
    return fetchData('https://api.example.com/data3');
  })
  .then(data3 => {
    console.log(data3); // Data from https://api.example.com/data3
  })
  .catch(error => {
    console.error(error);
  });

 


Promise 객체의 정적 메서드들

 

★Promise 클래스의 정적 메서드 all

  • 배열로 반환됨을 확인.
  • 진행 시간과 관계없이 순서대로 저장됨.
  • 하나라도 실패하게되면 실패값만 보여주게 됨.
Promise.all([
    new Promise(resolve => resolve(1)),//객체
    new Promise(resolve => resolve(2)),//객체
    new Promise(resolve => resolve(3))//객체
]).then(console.log); // 배열로 반환.

-> 배열로 반환됨을 볼 수 있다.

Promise.all([
    new Promise(resolve => setTimeout(() => resolve(10), 3000)),
    new Promise(resolve => setTimeout(() => resolve(20), 2000)),
    new Promise(resolve => setTimeout(() => resolve(30), 1000))
]).then(console.log); //시간과 관계없이 저장됨.

-> 시간과 관계 없이 저장됨을 볼 수 있다.

Promise.all([
    new Promise((resolve, reject) => resolve(1)),
    new Promise((resolve, reject) => resolve(2)),
    new Promise((resolve, reject) => reject(new Error('에러 발생'))),
]).then(
    console.log,
    err => console.log(err.message)
);

-> 하나라도 실패할경우 실패값만 보여주게된다.

 

★Promise 클래스의 정적 메서드 allSettled

  • 배열 요소중 실패가 있더라도 결과값을 보존하여 보임.

성공한 경우의 배열 요소 객체 형태 : { status: 'fulfilled', value: 결과값 }

실패한 경우의 배열 요소 객체 형태 : { status: 'rejected', reason: 에러객체 }

  • 실패값의 value 를 출력할경우 undefined 를 반환
Promise.allSettled([
    new Promise((resolve, reject) => resolve(1)),
    new Promise((resolve, reject) => resolve(2)),
    new Promise((resolve, reject) => reject(new Error('에러 발생'))),
]).then(
    console.log
);

Promise.allSettled([
    new Promise((resolve, reject) => resolve(1)),
    new Promise((resolve, reject) => resolve(2)),
    new Promise((resolve, reject) => reject(new Error('에러 발생'))),
]).then(
    result => {
        console.log(result[0].value);
        console.log(result[1].value);
        console.log(result[2].value);  
    }
);

 

-> 세개의  promise 상태값 또는 이유를 볼 수 있다.

 

★Promise 클래스의 정적 메서드 race

  • 성공과 실패에 관계없이 시간상 제일 먼저 실행되는 결과를 반환한다.
Promise.race([
    new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
    new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
    new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러 발생')), 1000)),
]).then(
    console.log,
    err => console.log(err.message)
);

 

->시간 관계상 가장 빨랐던 3번째 promise 의 결과 값을 반환한다.

 

다른 메서드들도 있으니 궁금하면 찾아보면 재밌을것 같다.

 


함수를 promise 형태로 써보자 !
async / await

왜 굳이 프로미스를 함수로 만들까 ?

함수를 사용하는 우리의 가장 큰 이유는 무엇인가,

1. 재사용성 ->  필요시에 재활용 하기 좋음 .

2. 가독성 -> 비동기시의 작업의 정의와 사용을 구분하기에 명시성이 좋아지고 코드 구조가 명확해진다.

3. 확장성 -> 인자를 전달하여 동적으로 비동기 작업들을 실행할 수 있다.

 

< async > 형식 : async function 함수명 (){}

 

function 키워드 앞에 async 를 명시함으로써 함수 내에서 반환된 값은

프라미스 형태로 반환되며, 프라미스가 아닌 일반값인 경우에도

프라미스로 감싼 형태로 반환되는 특성.

 

async function returnPromise1() {
    return '프라미스 성공';
}

returnPromise1().then(console.log);

returnPromise1() 함수를 promise 화 시켰다..

함수를 실행하면서 , 성공했을때의 비동기실행을 수행한다.


 

< await > 

프라미스 앞에 await 키워드를 명시함으로써

프라미스의 실행자가 완료될 때까지 await 명시 시점 이후의 로직을 대기하고

그 결과를 반환함으로써 then 을 대체하는 효과를 가져

then 메서드에서와 같이 콜백함수를 통한 별도의 매수변수를 통한 결과 반환이 필요치 않다.

단, await 은 async 로 지정된 함수 내에서만 사용 가능하다

또한 프라미스 객체 생성문의 인자가 되는 콜백 내에서와 같이

await 이전까지는 동기적으로 처리됨을 확인 가능하다.

 

async function returnPromise2() {
    console.log(1);

    const promise = new Promise(resolve => {
        setTimeout(() => resolve('성공'), 1000);
    });

    let result = await promise;

    console.log(result);
}

returnPromise2();

console.log(2);
  1. 함수가 호출되면 즉시 1이 콘솔에 출력됩니다.
  2. 프라미스가 생성되고, 1초 후에 "성공"으로 해결됩니다.
  3. await은  프라미스가 해결될 때까지 함수의 실행을 일시 정지시킵니다.
  4. 프라미스가 해결되면 "성공"이 result 변수에 저장됩니다.
  5. 마지막으로 result의 값인 "성공"이 콘솔에 출력됩니다.

< async 프라미스 실패시의 catch 메서드 >

 

프라미스 실패시의 예외처리가 되지 않은 프라미스 결과값은

catch 메서드를 통해 처리하여도 결과는 동하다.

catch 는 아래와 같이 성공에 대한 다중 then 메서드 호출 이후에

최종적인 에러처리를 위한 목적으로도 활용한다.

async function returnPromise3() {
    let promise;

    promise = new Promise((resolve, reject) => {
        setTimeout(() => reject('실패'), 2000);
        setTimeout(() => resolve('성공'), 2000);
    });

    return await promise; // 값을 전달. reject 실행으로 인해 .catch 실행
}

returnPromise3()
    .then(console.log)
    .catch(console.log);

 

 


async 함수 내의 try , catch

    async function test1() {
      try {
        let response = await fetch('n09_03parameterResult1.html');
        // => fetch 는 response  받았을때 예외처리를 해주지않기 때문에
        //    catch 블럭이 실행되지 않음
        
        //let responseData = response.json(); -> 오류발생, catch 블럭실행안됨
        let responseData = await response.json(); //-> 오류발생, catch 블럭에서 처리
      } catch (error) {
        console.log(`** Test1) error => ${error}`);
      }
    } //test1
    test1();

이 코드는 api 를 사용하여 데이터를 비동기적으로 가져오는 예제이다.

fetch 함수를 사용하여 데이터를 가져오고,

데이터를 파싱하는 부분에서 await을 사용하여 비동기적으로 처리하였다.

비동기적 처리를 await이 앞의 작업을 대기한다.

위의 비동기 작업에서 발생하는 모든 에러를 catch에서 처리.