(JS) Asynchronous

Mirrer·2022년 5월 2일
0

JavaScript

목록 보기
17/24
post-thumbnail

자바스크립트 실행 순서

자바스크립트는 하나의 스택을 가진 Single Thread언어

개발자가 코드를 작성한 뒤 실행하면 브라우저에 내장되있는 자바스크립트 엔진이 소스코드를 번역해서 실행한다.

그리고 동적으로 생성한 객체는 메모리 Heap에 저장한 뒤 Call Stack이라는 공간에 함수의 실행순서를 저장한다.

자바스크립트는 하나의 스택을 가진 Single Thread언어, 즉 기본적으로 작성한 순서대로 (동기적) 코드가 진행된다.

function a() {
  // long time work~~~
  for (let i = 0; i < 10000000000000000000; i++){}
  return 1;
}

function b() {
  return a() + 1;
}

function c() {
  return b() + 1;
}

console.log('코드 시작!'); // 코드 시작!
const result = c();
console.log(result); // a함수의 실행이 끝난뒤에야 3이 출력

위 코드를 보면 a함수에서 많은 시간이 할당되어 다른 코드로 제어권이 넘어가는데 오랜 시간이 걸린다.

위에서 말했듯 자바스크립트 엔진은 싱글 쓰레드이기 때문에 동기적인 작업만 수행한다.

그래서 호스트환경에서 제공하는 Web APIs함수를 통해 비동기적인 작업을 수행한다.


Asynchronous JavaScript

호스트환경에서 제공하는 Web APIs함수를 통해 비동기적인 작업을 수행한다고 했다.

Web APIs를 호출할 때는 콜백함수를 할당한다. 이 콜백함수는 내부적으로 비동기적인 작업을 수행한다.

Web APIs가 작업을 끝내면 등록한 콜백함수Task Queue에 할당한다.

이 후 자바스크립트 엔진의 Event LoopCall Stack (일반 함수)Task Queue (비동기 콜백 함수)를 주시하면서 Call Stack이 비어있을 때만 Task Queue에 있는 콜백함수를 Call Stack으로 이동시켜 실행한다.

function execute() {
  console.log('1');
  setTimeout(() => {
    console.log('2');
  }, 3000);
  console.log('3');
}

execute(); // 1 3 2


SetTimeout, SetInterval

SetTimeoutSetIntervalWeb APIs에서 제공하는 대표적인 비동기함수이다.


- SetTimeout

전역 setTimeout() 메서드는 타이머가 만료되면 함수 또는 지정된 코드를 실행하는 타이머를 설정한다.

setTimeout() 함수는 첫번째 인자로 실행할 코드를 담고 있는 함수(콜백 함수)를 받고, 두번째 인자로 지연 시간을 밀리초(ms) 단위로 받는다.

또한 세번째, 네번째....등등 인자로 콜백 함수에 전달할 추가 매개변수를 전달할 수 있다.

function add(x, y) {
  console.log(x + y);
}

// 2초뒤에 NaN출력
setTimeout(add, 2000);

// 3초뒤에 7출력
setTimeout(add, 3000, 3, 4); 

setTimeout() 함수는 Timeout ID리는 숫자를 반환하는데 이는 setTimeout() 함수를 호출할 때 마다 내부적으로 생성되는 타이머 객체를 가리킨다.

이 값을 인자로 clearTimeout() 함수를 호출하면 실행될 코드를 중단할 수 있다.

const timeoutID = setTimeout(() => {
  console.log("Hello World!!");
}, 2000);

clearTimeout(timeoutID); // 아무런 결과가 출력되지 않는다.

- setInterval

setInterval() 메서드는 각 호출 사이에 고정된 시간 지연으로 함수를 반복적으로 호출하거나 코드 조각을 실행한다.

setInterval() 함수는 setTimeout() 함수와 동일한 인자를 할당한다.

setInterval(() => console.log(new Date()), 2000);

// 2022-05-02T11:08:46.885Z
// 2022-05-02T11:08:48.891Z
// 2022-05-02T11:08:50.895Z
// 2022-05-02T11:08:52.900Z
// 2022-05-02T11:08:54.904Z
// 2022-05-02T11:08:56.917Z
//           :
//           :

setInterval() 함수 또한 인터벌 아이디(Interval ID)라고 불리는 숫자를 반환한다.

setInterval() 함수 또한 는 Interval ID리는 숫자를 반환하는데 이는 setInterval() 함수를 호출할 때 마다 내부적으로 생성되는 타이머 객체를 가리킨다.

이 값을 인자로 clearInterval() 함수를 호출하면 실행될 코드를 중단할 수 있다.

const intervalId = setInterval(() => console.log(new Date()), 2000);

clearInterval(intervalId); // 아무런 결과가 출력되지 않는다.

Promise

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 표현

Promise는 무겁고 오래걸리는 비동기적 작업을 코드 내부에서 수행할 수 있게 도와주는 객체이다.

비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있는데 이 때 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있다.

다만 최종 결과를 반환하는 것이 아닌, 미래의 어떤 시점에 결과를 제공하겠다는 '약속'(프로미스)을 반환한다.

Promise 객체는 내부적으로 resolve, reject를 사용하여 _비동기 작업 처리과정의 성공, 실패를 구분**_한다.


Promise 상태

  • pending : 프로미스가 생성되어 일이 끝나지 않은 상태

  • fulfilled : 비동기적 코드가 성공적으로 끝난 상태

  • reject : 실패한 상태

Promise 명령어

  • then : Promise가 성공적으로 실행된 뒤 실행될 함수

  • catch : Promise가 에러를 발생하면 실행될 함수

  • finally : 최종적으로 모든일이 끝났을 때 실행될 함수

function runInDelay(seconds) {
  return new Promise((resolve, reject) => {
    if (!seconds || seconds < 0) {
      reject(new Error('second가 0보다 작음'))
    }
    setTimeout((resolve), seconds * 1000);
  });
}

runInDelay(0)
.then(() => console.log('타이머 완료!'))
.catch(console.error)
.finally(() => console.log('끝났다!'))

Promise의 static함수

  • Primois.resolve : 프로미스 객체를 생성하고 바로 resolve코드 실행
function fetchEgg(chicken) {
  return Promise.resolve(`${chicken} = Egg!!`);
}

fetchEgg('Chicken')
.then((egg) => {console.log(egg)}) // Chicken = Egg!!
  • Primois.reject : 프로미스 객체를 생성하고 바로 reject코드 실행
function fetchEgg(chicken) {
  return Promise.resolve(`${chicken} = Egg!!`);
}

function fryEgg(egg) {
  return Promise.resolve(`${egg} = Fry!!`);
}

function getChicken() {
  // 에러 발생
  return Promise.reject(new Error('치킨을 가져올 수 없음'));
  // return Promise.resolve(`Garden = Chicken!!`);
}

// 에러 버블링으로 getChicken에서 발생한 에러는 마지막 fryEgg까지 전달
// 적절한 위치에 에러를 핸들링
getChicken()
.catch((error) => {
  console.log(error.name) // Error
  return 'Another Chicken';
})
.then((chicken) => { 
  return fetchEgg(chicken);
})
.then((egg) => fryEgg(egg))
.then((friedEgg) => console.log(friedEgg)) // Another Chicken = Egg!! = Fry!!
// .catch((error) => console.log(error.name)); // Error
  • Primois then체이닝 : then을 연달아 사용
function fetchEgg(chicken) {
  return Promise.resolve(`${chicken} = Egg!!`);
}

function fryEgg(egg) {
  return Promise.resolve(`${egg} = Fry!!`);
}

function getChicken() {
  return Promise.resolve(`Garden = Chicken!!`);
}

getChicken()
.then((chicken) => {
  return fetchEgg(chicken);
})
.then((egg) => fryEgg(egg))
.then((friedEgg) => console.log(friedEgg)); // Garden = Chicken!! = Egg!! = Fry!!
  • Primois.all : 병렬적으로 한번에 모든 Promise를 배열형태로 전달하여 실행
function getBanana() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Banana');
    }, 1000);
  });
}

function getApple() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Apple');
    }, 3000);
  });
}

// 바나나(3초) + 사과(1초) = 4초 소요
getBanana()
.then((banana) => 
  getApple()
  .then((apple) => [banana, apple])
)
.then(console.log); // [ 'Banana', 'Apple' ]

// 바나나(3초), 사과(1초)동시 실행 = 3초 소요
Promise.all([getBanana(), getApple()])
.then(console.log); // [ 'Banana', 'Apple' ]
  • Primois.race : 병렬적으로 한번에 모든 Promise를 배열형태로 전달하여 실행한다. 이후 제일 먼저 수행된 결과만 출력한다.
function getBanana() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Banana');
    }, 1000);
  });
}

function getApple() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Apple');
    }, 3000);
  });
}

Promise.race([getApple(), getBanana()])
.then(console.log); // Banana
  • Primois.allSettled : 병렬적으로 한번에 모든 Promise를 배열형태로 전달하여 실행한다. 이후 실행 결과를 배열로 출력한다.
function getBanana() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Banana');
    }, 1000);
  });
}

function getApple() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Apple');
    }, 3000);
  });
}

function getOrange() {
  return Promise.reject(new Error('no Orange!!'));
}

Promise.allSettled([getApple(), getBanana(), getOrange()])
.then(console.log)
.catch(console.log);
// 실행 결과
[
  { status: 'fulfilled', value: 'Apple' },     
  { status: 'fulfilled', value: 'Banana' },    
  {
    status: 'rejected',
    reason: Error: no Orange!!
        at getOrange (C:\Users\Administrator\OneDrive\Desktop\Training\main.js:18:25)
        at Object.<anonymous> (C:\Users\Administrator\OneDrive\Desktop\Training\main.js:21:46)        at Module._compile (node:internal/modules/cjs/loader:1097:14)
        at Object.Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
        at Module.load (node:internal/modules/cjs/loader:975:32)
        at Function.Module._load (node:internal/modules/cjs/loader:822:12)
        at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12) 
        at node:internal/main/run_main_module:17:47
  }
]

Async & Await

기존의 비동기 처리 방식의 단점을 보완하고 코드의 가독성 높이는 문법

async function 선언은 AsyncFunction객체를 반환하는 하나의 비동기 함수를 정의한다.

비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise를 사용하여 결과를 반환한다.

그러나 비동기 함수를 사용하는 코드의 구문과 구조는, 표준 동기 함수를 사용하는것과 흡사하다.

async function과 함께 사용되는 await연산자는 Promise를 기다리기 위해 사용된다.

await연산자는 async function 내부에서만 사용할 수 있다.


async & await 사용 방법

Promise를 사용할 함수 키워드 앞에 async키워드를 사용해 함수를 선언한다.

이후 함수 내부에서 Promise를 기다리는 구분에 await를 사용한다.

function getBanana() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Banana');
    }, 1000);
  });
}

function getApple() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Apple');
    }, 3000);
  });
}

function getOrange() {
  return Promise.reject(new Error('no Orange!!'));
}

async function fetchFruits() {
  const banana = await getBanana();
  const apple = await getApple();
  return [banana, apple];
}

fetchFruits()
  .then(console.log); // [ 'Banana', 'Apple' ]

JSON (JavaScript Object Notation)

복잡한 객체를 네트워크를 통해 출력하기 위해 문자열 또는 객체로 변환

JSON은 서버와 클러이언트(브라우저, 모바일) 간의 HTTP통신을 위한 오브젝트 형태의 텍스트 포맷이다.

JSON 객체는 JavaScript Object Notation(JSON)을 분석하거나 값을 JSON으로 변환하는 메서드를 가지고 있다.

이는 JSON을 직접 호출하거나 인스턴스를 생성할 수 없으며, 두 개의 메서드를 제외하면 자신만의 흥미로운 기능은 없다.


JSON객체 메서드

  • stringify(object) : JSON직렬화(Serializing), 객체를 문자열로 변환

  • parse(JSON) : object역직렬화(Deserializing), 문자열 데이터를 자바스크립트 객체로 변환

const mirrer = {
  name: 'mirrer',
  age: 30,
  eat: () => {
    console.log('eat');
  }
}

// 직렬화
const json = JSON.stringify(mirrer);
console.log(mirrer); // { name: 'mirrer', age: 30, eat: [Function: eat] }
console.log(json); // {"name":"mirrer","age":30}

// 역직렬화
const obj = JSON.parse(json);
console.log(obj); // { name: 'mirrer', age: 30 }

fetch API

HTTP 요청 전송 기능을 제공하는 Web API

Fetch API네트워크 통신을 포함한 리소스 취득을 위한 인터페이스가 정의되어 있다.

XMLHttpRequest 또한 비슷한 API가 존재하지만 Fetch API는 좀더 강력하고 유연한 조작이 가능하다.

fetch API 사용방법

fetch API는 네트워크로부터 특정한 자원(resource)를 받아올 수 있다.

이 때 resource는 서버에 있는 데이터, 이미지, 동영상..등등 모든요소가 해당된다.

fetch APIpromise를 반환하고 promiseResponse라는 객체를 반환한다.

Responsejson함수는 서버로 받은 데이터를 자바스크립트에서 처리할 수 있는 객체로 반환되고 반환된 객체는 promise이기 때문에 비동기적으로 처리한다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Coding Training</title>
</head>
<body>
  <script>
    fetch('https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0')
      .then((response) => response.json())
        .then((data) => console.log(data.dataseries));
  </script>
</body>
</html>

참고 자료

Asynchronous JavaScript - JavaScript | MDN
setTimeout() - Web API | MDN
setInterval() - Web APIs | MDN - Mozilla
Promise - JavaScript | MDN
async function - JavaScript | MDN
Await function - JavaScript | MDN
json - JavaScript | MDN
Fetch API - Web API | MDN
모던 자바스크립트 Deep Dive
모던 JavaScript 튜토리얼

profile
memories Of A front-end web developer

0개의 댓글