티스토리 뷰

728x90
반응형

[참고자료]

더보기

입문자에게 읽기 좋은 자료입니다. 천천히 이해할 때까지 계속 읽어보는걸 추천합니다.

 

 

자바스크립트 비동기 처리와 콜백 함수

(중급) 중급 자바스크립트 개발자가 되기 위한 자바스크립트 비동기 처리와 콜백 함수 이해하기. 콜백 지옥과 해결 방법 등

joshua1988.github.io

 

자바스크립트 Promise 쉽게 이해하기

(중급) 자바스크립트 입문자를 위한 Promise 설명. 쉽게 알아보는 자바스크립트 Promise 개념, 사용법, 예제 코드. 예제로 알아보는 then(), catch() 활용법

joshua1988.github.io

 

자바스크립트 async와 await

(중급) 자바스크립트 개발자를 위한 async, await 사용법 설명. 쉽게 알아보는 자바스크립트 async await 개념, 사용법, 예제 코드, 예외 처리 방법

joshua1988.github.io

 

 

[Javascript] 비동기, Promise, async, await 확실하게 이해하기 – Under The Pencil

개요 본 글은 자바스크립트에서 Promise 에 대한 개념을 잡기 위해 작성한 글입니다. 자바스크립트의 기본 문법을 먼저 알아야 이 글을 조금 더 수월하게 보실 수 있습니다. 필자는 Node.js 기반에서

elvanov.com


 

Using Promises - JavaScript | MDN

A Promise is an object representing the eventual completion or failure of an asynchronous operation. Since most people are consumers of already-created promises, this guide will explain consumption of returned promises before explaining how to create them.

developer.mozilla.org

 

[JavaScript] 바보들을 위한 Promise 강의 - 도대체 Promise는 어떻게 쓰는거야?

들어가며 JavaScript의 세계에서는 거의 대부분의 작업들이 비동기로 이루어진다. 어떤 작업을 요청하면서 콜백 함수를 등록하면, 작업이 수행되고 나서 결과를 나중에 콜백 함수를 통해 알려주는

programmingsummaries.tistory.com

 

 

머리속으로는 이해가 되는데, 이해한 것을 바탕으로 글이나 말로 설명하는 것이 서툴러서 실제의 정의와 제가 이 글에서 정의한 내용이 일치하지 않을 우려가 있습니다. 틀린부분이 있다면 코멘트로 알려주시면 감사하겠습니다. :)

이해가 안되는부분이라던가 질문도 괜찮습니다 :)


비동기처리와 콜백함수

 

비동기 처리

특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성을 의미합니다.
비동기 처리는 특정 로직의 실행이 끝날 때까지 기다려주지 않고, 나머지 코드를 먼저 실행하는 것을 의미합니다.

 

비동기 처리는 언제 필요한가요?

  • Ajax Web API 요청
  • 암호화/복호화
  • 파일읽기
  • 작업 예약

 

콜백함수

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

Note, however, that callbacks are often used to continue code execution after an asynchronous operation has completed - these are called asynchronous callbacks.

[출처] mdn web docs_

 

콜백함수는 

함수를 값으로서 변수에 담거나
또 다른 함수의 파라미터(인자)로서 담을 수 있고
함수를 반환값으로 리턴
할 수 있습니다.

자바스크립트에서 비동기처리에서는 콜백함수를 사용됩니다.

// synchronus callback

function greeting(name) {
	alert(`Hello, ${name}`);
}

function processUserInput(callback) {
	const name = prompt("Please enter your name");
	callback(name);
}

processUserInput(greeting);

processUserInput(greeting) 에서 greeting() 함수를  callback 인자에 담아서 저장합니다.


콜백지옥

function increasedAndPrint(n, callback) {
  // 1초동안, n값을 1씩 증가시키고, increased 값을 출력
  setTimeout(() => {
    const increased = n + 1;
    console.log(increased);

    if (callback) {
      callback(increased);
    }
  }, 1000);
}

// 초기값은 0일 때, 5초 후 increased 값을 출력해보고 싶다.
console.log("작업 시작!");

increasedAndPrint(0, (n) => {
  increasedAndPrint(n, (n) => {
    increasedAndPrint(n, (n) => {
      increasedAndPrint(n, (n) => {
        increasedAndPrint(n, (n) => {
          console.log("작업끝");
        });
      });
    });
  });
});

 

특히 반복적인 작업을 요청할 때, 함수안에 콜백함수를 계속 불러야되는 번거로움이 있습니다. 콜백함수 안에 다른 콜백함수를 호출하여 꼬리에 꼬리를 무는 형식으로 코드의 길이가 길어지고 읽기가 불편합니다. 콜백지옥의 큰 단점은 코드의 가독성도 떨어지고 로직을 변경하기 어렵습니다.


Promise 객체

Promise 객체는 자바스크립트 비동기처리에 사용되는 객체 입니다. Promise 객체는 resolve 와 reject를 인자를 갖습니다.
resolve() 가 호출되면 Promise 가 대기상태에서 이행상태로 넘어가고, then()을 이용하여 처리결과값을 받을 수 있습니다.
반대로, reject() 는 호출 실패상태가 되어, catch()문에서 에러 처리를 합니다. 

 

Promise Chaining

then() 문으로 여러개의 Promise를 연결하여 처리할 수 있습니다.

//Producer
const getHen = () => new Promise((resolve, reject) => {
	setTimeout(() => resolve('hen'), 1000)
});

const getEgg = hen => new Promise((resolve, reject) => {
	setTimeout(() => resolve(`${hen} => egg`), 1000);
});

const cook = egg => new Promise((resolve, reject) => {
	setTimeout(() => resolve(`${egg} => meal`), 1000);
});

//Consumer
getHen()
	.then(getEgg)
	.then(cook)
	.then((meal) => console.log(meal));

//실행결과 : (3초 후) 'hen => egg => meal'

 

Error Handling

Promise의 catch() 문으로 에러를 핸들링 합니다.

 

[Promise 예제코드 1.1]

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve() -> myPromise.then()에 정의된 함수에서 콜백처리
    resolve(0);

    // reject() -> myPromise.catch()에 정의된 함수에서 에러핸들링
    reject("Promise Failed");
  }, 1000);
});

myPromise
  .then((n) => {
    const increased = n + 1;
    console.log(increased);	// 1
  })
  .catch((error) => {
    console.error(error);
  });

 

setTimeout 내부함수에서 에러가 없다면 resolve가 가리키는 콜백함수에서 처리를 합니다. 
resolve가 가리키는 콜백함수는 myPromise.then() 함수입니다. [Promise 예제코드 1.1] 에서 출력되는 콘솔 결과는 1 입니다.

 

[Promise 예제코드 1.2]

setTimeout 내부함수에서 에러가 발생시켜봅시다.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 오류가 발생하지 않을 때 resolve() -> myPromise.then()에 정의된 함수에서 콜백처리
    resolve(0);

    throw new Error("Error 발생 테스트");
    // 오류가 발생했을 때는 reject() -> myPromise.catch()에 정의된 함수에서 에러핸들링
    reject("Promise Failed");
  }, 1000);
});

myPromise
  .then((n) => {
    const increased = n + 1;
    console.log(increased);
  })
  .catch((error) => {
    console.error(error);
  });

setTimeout 내부함수에서 에러가 발생했을 때, 위치 상관없이 브라우저에서 에러를  캐치하여 발생시켜 에러페이지를 나타냅니다.

 

 

[Promise 예제코드 1.3]

myPromise.then() 내부 콜백함수에서 에러를 발생시켜봅시다.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 오류가 발생하지 않을 때 resolve() -> myPromise.then()에 정의된 함수에서 콜백처리
    resolve(0);

    // 오류가 발생했을 때는 reject() -> myPromise.catch()에 정의된 함수에서 에러핸들링
    reject("Promise Failed");
  }, 1000);
});

myPromise
  .then((n) => {
    const increased = n + 1;
    throw new Error("Error 발생 테스트 - then() 내부에서 발생");
    console.log(increased);
  })
  .catch((error) => {
    console.error(error);
  });

myPromise.then() 콜백함수 처리를 하다가 에러가 발생하면, 해당 에러를 error 인자에 담아서  catch 함수로 바로 에러를 핸들링하게 됩니다. 위의 예제코드에서는 catch문에서 에러처리를 하고, 콘솔에서 에러를 출력하는 형태로 되어있습니다.

 

 

[Promise 예제코드 2.1]

function increasedAndPrint(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const increased = n + 1;
      if (increased === 5) {
        const error = new Error();
        error.name = "ValueIsFiveError";
        reject(error);
        return;
      }

      console.log(increased);
      resolve(increased);
    }, 1000);
  });
}

// 5초 동안 실행하려면
increasedAndPrint(0)
  .then((n) => {
    return increasedAndPrint(n);
  })
  .then((n) => {
    return increasedAndPrint(n);
  })
  .then((n) => {
    return increasedAndPrint(n);
  })
  .then((n) => {
    return increasedAndPrint(n);
  })
  .catch((error) => {
    // 5초 후 에러 발생
    console.error(error); // Error: ValueIsFiveError
  });

 

 

[Promise 예제코드 2.2]

[Promise 예제코드 2.1] 에서 then()처리 함수의 경우에는, 프로미스 객체를 리턴하는 콜백함수 이름만 나타내도
[Promise 예제코드 2.1] 와 동일한 결과를 나타냅니다.

function increasedAndPrint(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const increased = n + 1;
      if (increased === 5) {
        const error = new Error();
        error.name = "ValueIsFiveError";
        reject(error);
        return;
      }

      console.log(increased);
      resolve(increased);
    }, 1000);
  });
}

increasedAndPrint(0)
  .then(increasedAndPrint)
  .then(increasedAndPrint)
  .then(increasedAndPrint)
  .then(increasedAndPrint)
  .catch((error) => {
    console.error(error); // Error: ValueIsFiveError
  });

 

[Promise 예제코드 2.1] 과 [Promise 예제코드 2.2]의 출력결과는 동일합니다.
increased 값이 5일 때 사용자정의 에러 ValueIsFiveError 를 catch문에서 처리로 콘솔에서 에러를 출력하도록 핸들링 했습니다.

1
2
3
4
ValueIsFiveError

 

Promise 객체를 사용한 비동기처리 코드의 단점은 3가지 입니다.

  • 어떤부분에서 에러가 발생하는지 파악하기가 어렵다.
  • 특정 조건에 따라 분기를 나누는게 어렵다.
  • 특정작업을 공유해가면서 작업하기가 어렵다.

async 와 await 

async await은 Promise와 콜백함수 방법의 단점을 보완하여 개발자가 읽기 좋은 코드의 형태를 갖추게 됐습니다.
코드를 읽어야하는 개발자의 경우에는 순서대로 위에서 아래 방향으로 진행하는 것을 선호합니다.

async와 await의 기본문법은 아래와 같습니다.

async function 함수명 () {
	await 비동기처리_메소드명();
}
const 함수명 = async () => {

	await 비동기처리_메소드명();
}

async 예약어가 붙인 함수에 await 예약어를 붙여서 Promise를 리턴하는 함수를 호출하면 코드의 실행결과도 순차적으로 진행됩니다.

try-catch 문에서 로직을 처리를 하는게 좋습니다.

const 함수명 = async () => {
	try {
    
		const result = await 비동기_메소드명();
        console.log(result);
        
	} catch(error) {
		console.error(error);
	}
}

 


Promise.all 과 Promise.race

Promise.all

Promise.all() 은 여러개의 Promise 들을 동시에 처리합니다.

 

[Promise.all 예제코드 1] Promise.all 적용이전

function sleep(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

const getDog = async () => {
  const time = 1000;
  await sleep(time);
  return `개 ${time}ms`;
};

const getRabbit = async () => {
  const time = 800;
  await sleep(time);
  return `토끼 ${time}ms`;
};

const getTurtle = async () => {
  const time = 5000;
  await sleep(time);
  return `거북이 ${time}ms`;
};

async function process() {
  try {
    const dog = await getDog();
    console.log(dog);

    const rabbit = await getRabbit();
    console.log(rabbit);

    const turtle = await getTurtle();
    console.log(turtle);
  } catch (error) {
    console.error(error);
  }
}

process();

 

아무리 async-await 비동기처리 함수를 사용한다해도, 위의 코드 결과는 위에서 아래로 순차적으로 처리됩니다.

[출력결과]

개 1000ms
토끼 800ms
거북이 5000ms

 

 

[Promise.all 예제코드 2]

function sleep(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

const getDog = async () => {
  const time = 1000;
  await sleep(time);
  return `개 ${time}ms`;
};

const getRabbit = async () => {
  const time = 800;
  await sleep(time);
  return `토끼 ${time}ms`;
};

const getTurtle = async () => {
  const time = 5000;
  await sleep(time);
  return `거북이 ${time}ms`;
};

async function process() {
  try {
    const start = Date.now();
    const results = await Promise.all([getDog(), getRabbit(), getTurtle()]);
    console.log(results);	// ["개 1000ms", "토끼 800ms", "거북이 5000ms"]
    const end = Date.now();
    
    const duration = end - start;
    console.log(duration);  // 500x ms
    
  } catch (error) {
    console.error(error);
  }
}

process();


Promise.all 을 이용하여 배열의 요소를  모두처리하는데 걸리는데 소요된 시간은 5초 입니다.
[Promise.all 예제코드1] 에서 수행하는데 걸리는시간은  세개의 함수 소요시간의 합인 약 6.8초 입니다.

추가적으로 비구조화 할당을 통해서, 배열에 있는 원소를 각각 매핑할 수 있습니다.
아래 코드를 예를 들면, dog는 getDog() 결과값에 rabbit은 getRabbit() 결과값에 매핑됩니다.

try {

	const [dog, rabbit, turtle] = await Promise.all([ getDog(), getRabbit(), getTurtle() ]);

	console.log(dog);	// 개 1000ms
	console.log(rabbit);	// 토끼 800ms
	console.log(turtle);	// 거북이 500ms
    
} catch (error) {
	console.error(error);
}

[Promise.all 예제코드1] 의 결과와 동일하게 getDog(), getRabbit(), getTurtle() 로 정의된 순서대로 처리를 하여
동일한 출력결과를 갖습니다. 만일 Promise.all 의 요소중 하나 에서 에러가 발생하게되어 다음요소로 넘어가지 않고, catch문에서 에러처리를 하게됩니다.

 

 

Promise.race

Promise.race()는 처리속도가 빠른 Promise를 우선으로 처리합니다.
Promise.all 과 다르게 가장 빠른 Promise에서 에러가 발생하면 에러로 간주합니다.
그러나 가장 빠른 Promise 에서는 에러가 없고, 그 다음 요소에서 에러가 있더라도 에러로 간주하지 않습니다.
여러개의 프로세스중 한개를 먼저 종료해야되는 상황일 때, Promise.race 를 사용합니다.

function sleep(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

const getDog = async () => {
  const time = 1000;
  await sleep(time);
  return "개";
};

const getRabbit = async () => {
  const time = 800;
  await sleep(time);
  return "토끼";
};

const getTurtle = async () => {
  const time = 5000;
  await sleep(time);
  return "거북이";
};

async function process() {
  try {
  
    const results = await Promise.race([getDog(), getRabbit(), getTurtle()]);
    console.log(results);	// 토끼

  } catch (error) {
    console.error(error);
  }
}

process();

 

 

 

728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함