티스토리 뷰

목차

  1. 콜백과 비동기 프로그래밍
  2. Promise
  3. async와 await
  4. 비동기 순회

1.  콜백과 비동기 프로그래밍

 

콜백 함수란?

다른 함수에 매개변수로 넘겨진 함수

함수 안에서 실행하는 또 다른 함수

 

function say(callback) {
  callback();
}

const hi = function () {
  console.log("hi");
};

say(hi);

 

비동기 프로그래밍이란?

특정 코드의 연산이 끝날 때까지 기다리지 않고,그 동안 다른 코드를 실행하는 것

 

ex) 특정 파일을 다운받을 동안 다른 코드를 실행

 

console.log(1);

setTimeout(() => {
  console.log(2);
}, 3000);

console.log(3);

//1 3 2

 

 

1.1  타이머

setTimeout()

함수는 첫번째 인자로 실행할 코드를 담고 있는 함수를 받고,

두번째 인자로 지연 시간을 밀리초(ms) 단위로 받음

 

setTimeout (()=>console.log('hi'), 60000);

//1분이 지나면 함수를 호출

 

setlnterval()

setTimeout()은 인자는 전달하지 않고 지정된 콜백 함수를 한 번 호출하고서, 그 함수에 대해 잊어버림

반복적으로 실행하는 업데이트를 체크하는 함수가 필요하다면 setlnterval()을 사용

 

// 2초 간격으로 메시지를 보여줌
let timerId = setInterval(() => alert('째깍'), 2000);

// 5초 후에 정지
setTimeout(() => { clearInterval(timerId); alert('정지'); }, 5000);

 

1.2 이벤트

웹 브라우저는 사용자가 키를 누르고, 마우스를 움직이고, 마 우스 버튼을 클릭하고,

터치스크린을 터치할 때 이벤트를 발생

 

이벤트 주도 자바스크립트 프로그램은 지정된 컨텍스트에 지정된 타입의 이벤트를 처리할 콜백 함수를 등록하고,
웹 브라우저는 지정된 이벤트가 일어날 때마다 함수를 호출

이런 콜백 함수를 이벤트 핸들러, 이벤트 리스너라고 부르며 (addEventListener)를 통해 등록

 

// CSS 선택자에 일치하는 HTML <button> 요소를 나타내는 객체를 웹 브라우저에 요청합니다.
let okay = document.querySelector("#confirmUpdateDialog button.okay");
// 사용자가 버튼을 클릭하면 호출될 콜백 함수를 등록합니다.
okay.addEventListener("click", applyUpdate);

 

 

 

2.  Promise

Promise란?

비동기 작업의 결과를 나타내는 객체

 

 

콜백 기반 비동기 프로그래밍의 심각한 문제는 콜백 안에 콜백이,

그 안에 또 콜백이 이어지는 형태가 계속되면 너무 심하게 들여쓰기 되어 코드를 읽기 어려워지는,

 콜백 지옥이 발생하며, 에러처리도 어려워 지는 단점이 발생

Promise를 사용한다면, 중첩된 콜백 대신 선형에 가까운 Promise 체인으로 바꿀 수 있음

 

2.1  Promise 사용

프라미스 객체에는 then()인스턴스 메서드가 존재

프라미스 객체를 변수에 할당하는 중간 단계를 거치지 않고,

프라미스를 반환하는 함수 호출에 .then()을 직접 이어붙이는 형태로 주로사용

 

 

프로미스 흐름도

 

프로미스의 3가지 상태

 

대기(pending):

이행하지도, 거부하지도 않은 초기 상태.

new Promise() //최초 호출 시 대기 상태

 

이행(fulfilled):

연산이 성공적으로 완료됨.

 

function getData() {
  return new Promise(function (resolve, reject) {
    var data = 100;
    resolve(data);
  });
}

getData().then(function (resolvedData) {
  console.log(resolvedData); // 100
});

이행 상태는 코드를 성공하는 것을 의미하며
 .then()을 이용하여 처리 결과 값을 받을 수 있고, 여러 개의 프로미스를 연결하는 chaining이 가능하다.

 

 

거부(rejected):

연산이 실패함.

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}

// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

코드를 처리하는데 오류가 발생한 것을 의미 , reject되면 .catch()를 이용해서 예외처리 가능

 

 

Promise Chain

프라미스의 가장 중요한 장점 중 하나는 비동기 작업 시퀀스를 then()의 체인으로 이어서 콜백 지옥을 방지

const fetchNum = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNum
  .then((num) => num * 2)
  .then((num) => num * 3)
  .then((num) => {
  	//리턴 값에 직접 프로미스 객체를 넣어도 됌
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num - 1), 1000);
    });
  })
  .then((num) => console.log(num));

//5

 

 

Promise 해석

function c1(response) {
  let p4 = response.json(); // 콜백 1
  return p4; // 프라미스 4를 반환합니다.
}
function c2(profile) {
  	// 콜백 2
	console.log(profile)
}
let p1 = fetch("https://jsonplaceholder.typicode.com/comments"); // 프라미스 1, 작업 1
let p2 = p1.then(c1); // 프라미스 2, 작업 2
let p3 = p2.then(c2); // 프라미스 3, 작업 3


// chaining으로 바로 표현 시
// let getData = fetch("https://jsonplaceholder.typicode.com/comments")
//   .then((res) => res.json())
//   .then((json) => console.log(json));

 

프라미스와 에러

프라미스가 거부됐을 때 두 번째 콜 백 함수에 전달되는 인자는 거부 사유를 나타내는 값이며,

일반적으로 Error 객체를 나타냄

 

프라미스 관련 에러는 일반적으로 프라미스 체인에 .catch()메서드를 추가 하는 식으로 처리

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}

// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

 

 

catchfinally 메서드

catch()

프라미스의 .catch() 메서드는 null을 첫 번째 인자로,

에러 처리 콜백을 두 번째 인자로 전달하여 .then()을 호출하는 것을 축약한 형태

 

//프라미스 p와 콜백 c가 있을 때 다음 두 행은 동등
p.then(null, c)
p.catch(c);

 

catch()는 프라미스 체인 어디에서 사용 해도 유효하며,

catch()에 전달된 에러는 프라미스 체인을 타고 내려가지 않음

 

 

finally

ES2018 이후 프라미스 객체에는 finally() 메서드가 생김

 

프라미스의 이행 여부와 관계 없이 파일이나 네트워크 연결을 닫는 것과 같은 정리 작업을 해야 한다면,

finally() 콜백이 이상적

 

finally() 메소드는 Promise 객체를 반환

Promise가 처리되면 충족되거나 거부되는지 여부에 관계없이 지정된 콜백 함수가 실행

이것은 Promise가 성공적으로 수행 되었는지 거절되었는지에 관계없이,

Promise가 처리 된 후에 코드가 무조건 한 번은 실행되는 것을 제공

 

let isLoading = true;

//fetch는 promise 객체를 반환하는 함수
fetch(myRequest).then(function(response) {
    var contentType = response.headers.get("content-type");
    if(contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then(function(json) { /* process your JSON further */ })
  .catch(function(error) { console.log(error); })
  .finally(function() { isLoading = false; });

아래 코드는 fetch의 결과에 상관없이 .finally()구문이 실행되어서 isLoading의 상태는 false

 

병렬 프라미스

비동기 작업을 병렬로 실행해야 할 때도 생길 경우,

Promise.all()이 프라미스의 병렬 실행을 담당

 

입력 프라미스 중 하나라도 거부되면 반환된 프라미스 역시 거부

 

여러 URL에서 텍스트 콘텐츠를 가져오는 코드

// URL 배열로 시작합니다.
const urls = [
  "https://koreanjson.com/posts/1",
  "https://koreanjson.com/posts/2",
  "https://koreanjson.com/posts/3",
  "https://koreanjson.com/posts/4",
  "https://koreanjson.com/posts/5",
]; // 프라미스 객체의 배열로 변환합니다.
promises = urls.map((url) => fetch(url).then((r) => r.text()));
// 이 프라미스 전체를 병렬로 실행하는 프라미스
Promise.all(promises)
  .then((bodies) => {
    /* 문자열 배열을 사용할 코드 */
    console.log(bodies);
  })
  .catch((e) => console.error(e));

 

 

ES2020 Promise.allSettled()를 도입

Promise.allSettled()는 반환된 프라미스를 절대 거부하지 않으며,

입력 프라미스 전체가 완료되기 전에는 이행되지 않음

 

Promise.allSettled([Promise.resolve(1), Promise.reject(2), 3]).then(
  (results) => {
    console.log(results);
    results[0]; //=>{status:"fulfilled", value: 1 }
    results[1]; // => { status: "rejected", reason: 2 }
    results[2]; // => { status: "fulfilled", value: 3 }
  }
);

 

프라미스 시퀀스

 

프라미스 체인은 일정 숫자의 연속된 프라미스를 쉽게 처리 가능

하지만 임의의 숫자의 프라미스를 순서대로 실행하기는 쉽지 않음

배열 길이가 정해져 있지 않고 콘텐츠도 알 수 없다면 프라미스 체인을 미리 만 들 수 없으므로,

다음과 같이 동적으로 만들어야 함

 

function fetchSequentially(urls) {
  const bodies = [];
  function fetchOne(url) {
    return fetch(url)
      .then((res) => res.text())
      .then((body) => {
        // 바디를 배열에 저장하고 의도적으로 반환 값을 생략합니다.
        bodies.push(body);
      });
  }

  let p = Promise.resolve(undefined);
  // 체인 각 단계에서 URL을 하나씩 가져옵니다.
  for (let url of urls) {
    p = p.then(() => fetchOne(url));
  }

  return p.then(() => {
    console.log(bodies);
    return bodies;
  });
}

const inputUrl = [
  "https://koreanjson.com/posts/1",
  "https://koreanjson.com/posts/2",
  "https://koreanjson.com/posts/3",
  "https://koreanjson.com/posts/4",
  "https://koreanjson.com/posts/5",
];

fetchSequentially(inputUrl);

 

 

 

3. async와 await

ES2017은 새 키워드 async와 await를 도입

 

async와 await는 프라미스 사용을 극적으로 단순화하며, 프라미스 기반의 비동기 코드를 동기적 코드처럼 작성

 

 

await표현식

await 키워드는 프라미스를 받아 반환 값이나 예외로 바꿈

 

await 키워드는 프로그램 흐름을 차단하지 않으며,

지정된 프라미스가 완료되기 전 에는 말 그대로 ‘아무 일도 하지 않는다’는 점을 이해하는 것이 중요

 

await를 사용하는 코드는 항상 비동기적

 

 

async 함수

await 키워드는 async 키워드로 선언된 함수 안에서만 사용할 수 있음

async function getData() {
  let response = await fetch("https://jsonplaceholder.typicode.com/comments");
  let data = await response.json();
  console.log(data);
  return data;
}

getData();

 

 

함수를 async로 선언하면 설령 함수 바디에 프라미스 관련 코드가 전혀 없더라도 반환 값은 프라미스

 

async function a(){
    const x = await console.log(1)
    return x
}

a()
//반환 값은 Promise

 

async 키워드는 어떤 함수에든 사용 가능

function 키워드를 문으로 썼든 표현식으로 썼든 상관 X

화살표 함수, 클래스와 객체 리터럴의 단축 메서드에서도 동작

 

 

여러 개의 프라미스 대기

 

두 값을 동시에 가져올 수 있어야 함

async 함수는 프라미스에 기반하므로 Promise.all()로 처리가능

const callback = async () => {
  let url1 = "https://koreanjson.com/posts/1";

  let url2 = "https://koreanjson.com/posts/2";

  async function getJSON(url) {
    let response = await fetch(url);
    let body = await response.json();
    return body;
  }
  let [valuel, value2] = await Promise.all([getJSON(url1), getJSON(url2)]);
  console.log(valuel, value2);
};

callback();

 

 

4. 비동기 순회

 

for/await루프


  const params = [1, 2, 3, 4];

  const resArray = [];
  params.forEach(async (param) => {
    const data = await fetch(`https://koreanjson.com/posts/${param}`);

    resArray.push(await data.json());
    console.log(resArray)
  });

  console.log('완료');

for, forEach에서는 모든 비동기 작업이 끝나는 것을 대기하지 않음

 

 

for/await 루프 역시 프라미스 기반

 

비동기 이터 레이터는 프라미스를 생성하고,

for/await 루프는 프라미스가 이행되길 기다렸다가 이행된 값을 루프 변수에 할당하고 루프 바디를 실행.

그리고 이터레이터에서 다른 프라미스를 받아 새 프라미스가 이행되길 기다렸 다가 다시 시작

 

배열은 이터러블이므로 프라미스 배열도 일반적인 for/of 루프로 순회 가능

const callback = async () => {


  const params = [1, 2, 3, 4];

  const resArray = [];
  for await (const param of params) { //for of 도 동일한 결과
    const data = await fetch(`https://koreanjson.com/posts/${param}`);
    resArray.push(await data.json());
    console.log(resArray)
  }

  console.log('완료!'); //[1,2,3,4]결과 순서
};
callback();

 

 

비동기 이터레이터

이터러블 객체에는 심벌 이름 Symbol.iterator를 가진 메서드가 있음

이터레이터 객체에는 next() 메서드가 있는데, 이를 반복적으로 호출해 이터러블 객체에서 값을 가져올 수 있음

 

비동기 이터러블 객체에는 심벌 이름 Symbol.asynciterator 를 가진 메서드가 있음

for/await는 일반적인 이터러블 객체와도 호환되지만 비동기 이터러블 객체를 선호하며 Symbol.iterator 메서드보다 Symbol.asynciterator 메서드를 먼저 시도

 

 

비동기 이터레이터의 next() 메서드는 직접적으로 순회 결과 객체를 반환하는 것이 아니라

순회 결과 객체로 해석 되는 프라미스를 반환

 

이터레이터async 이터레이터

이터레이터를 제공해주는 메서드 Symbol.iterator Symbol.asyncIterator
next()가 반환하는 값 모든 값 Promise
반복 작업을 위해 사용하는 반복문 for..of for await..of
 
let range = {
  from: 1,
  to: 5,

  // for await..of 최초 실행 시, Symbol.asyncIterator가 호출됩니다.
  [Symbol.asyncIterator]() { // (1)
    // Symbol.asyncIterator 메서드는 이터레이터 객체를 반환합니다.
    // 이후 for await..of는 반환된 이터레이터 객체만을 대상으로 동작하는데,
    // 다음 값은 next()에서 정해집니다.
    return {
      current: this.from,
      last: this.to,

      // for await..of 반복문에 의해 각 이터레이션마다 next()가 호출됩니다.
      async next() { // (2)
        //  next()는 객체 형태의 값, {done:.., value :...}를 반환합니다.
        // (객체는 async에 의해 자동으로 프라미스로 감싸집니다.)

        // 비동기로 무언가를 하기 위해 await를 사용할 수 있습니다.
        await new Promise(resolve => setTimeout(resolve, 1000)); // (3)

        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

(async () => {

  for await (let value of range) { // (4)
    alert(value); // 1,2,3,4,5
  }

})()
 
 

비동기 제너레이터

async로 제너레이터 함수를 선언

비동기 제너레이터는 비동기 함수의 특징과 제너레이터의 특징을 모두 가짐

 

일반적인 비동기 함수와 마찬가지로 그 안에서 await를 사용할 수 있고,

일반적인 제너레이터와 마찬가지로 yield를 사용 가능

 

yield로 전달하는 값은 자동으로 프로미스가 됌

문법 또한 마찬가지로 async function과 function을 조합해 async function을 사용

 

async function* generateSequence(start, end) {

  for (let i = start; i <= end; i++) {

    // await를 사용할 수 있습니다!
    await new Promise(resolve => setTimeout(resolve, 1000));

    yield i;
  }

}

(async () => {

  let generator = generateSequence(1, 5);
  for await (let value of generator) {
    alert(value); // 1, 2, 3, 4, 5
  }

})();

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함