비동기 호출

위 예시를 읽고, 아래 개념을 대략적으로 이해할 수 있다.
blocking / non-blocking
synchronous / asynchronous
callback 함수 전달 방법을 알고 있다.
method chaining(Array의 map, filter 등을 연결해서 쓰는 법)을 할 수 있다.

Achievement Goals

어떤 경우에 중첩된 callback이 발생하는지 이해할 수 있다.
중첩된 callback의 단점, Promise의 장점을 이해할 수 있다.
Promise 사용 패턴을 이해할 수 있다.
resolve, reject의 의미와, then, catch와의 관계를 이해할 수 있다.
Promise에서 인자를 넘기는 방법을 이해할 수 있다.
Promise의 세 가지 상태를 이해할 수 있다.
Promise.all 의 사용법을 이해할 수 있다.
async/await keyword에 대해 이해하고, 작동 원리를 이해할 수 있다.
Node.js의 fs 모듈의 사용법을 이해할 수 있다.
blocking / non-blocking은 호출되는 함수가 바로 리턴하는지 아닌지!

blocking

하나의 작업이 끝날 때까지 이어지는 작업을 막는 것

non-blocking

동기 vs 비동기

호출되는 함수의 작업 완료 여부를 누가 신경 쓰느냐

동기 Syncronous

요청에 대한 결과가 동시에 일어남
작업이 끝날 때까지 기다리는 동안 중지상태로 다른 작업을 할 수 없음

비동기 Asynchronous

요청에 대한 결과가 동시에 일어나지 않음
특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행

JavaScript Single Thread

callback in action : 반복 실행하는 함수 (iterator), 이벤트에 따른 함수 (event handler)
callback은 함수 자체를 연결하는 것이지 함수 실행을 연결시켜서는 안된다.
blocking 요청에 대한 결과가 동시에 일어남
asynchronous 요청에 대한 결과가 동시에 일어나지 않는다
DOMContentLoaded
애니메이션 API
AJAX
Node.js를 만든 개발자도 위 대안이 합리적이라고 생각했습니다. 그래서 Node.js를 non-blocking하고 비동기적(asynchronous)으로 작동하는 런타임으로 개발하게 됩니다.
JavaScript의 비동기적 실행(Asynchonous execution)이라는 개념은 웹 개발에서 특히 유용합니다. 특히 아래 작업은 비동기적으로 작동되어야 효율적입니다. 익숙해져야겠죠?
백그라운드 실행, 로딩 창 등의 작업
인터넷에서 서버로 요청을 보내고, 응답을 기다리는 작업
큰 용량의 파일을 로딩하는 작업

비동기 함수 전달 패턴

Callback 패턴

let request = 'caffelatte'; orderCoffeeAsync(request, function(response) { // response drink(response); })
JavaScript
복사

이벤트 등록 패턴

let request = 'caffelatte'; orderCoffeeAsync(request).onready = function(response) { // response drink(response); }
JavaScript
복사

비동기의 주요사례

DOM Element의 이벤트 핸들러 : 마우스, 키보드 입력, 페이지 로딩
타이머 : 타이머 API(setTimeout 등), 애니메이션 API(requestAnimationFrame)
서버에 자원 요청 및 응답 : fetch API, AJAX (XHR)
JavaScript에서 비동기 흐름을 처리하는 방식은 3가지가 있다.

Callback

비동기 작업이 많아질경우 코드가 복잡해지며 callback hell에 빠지게..된다...

Promise

비동기 함수로부터 동기적으로 반환되는 객체
ES6에서 Promise가 표준에 추가됨

상태

3가지 상태로 나눌 수 있음
Fulfilled : 연산이 성공적으로 완료되어 기대한 값을 얻을 수 있는 상태, onFulfilled()가 호출됨 resolve()
Rejected : 연산이 실패하여 기대한 값을 얻지 못하는 상태, onReject()가 호출됨 reject()
Pending : 아직 fulfilled이나 rejected 상태가 아님

특징

Settled 상태의 Promise는 불변성을 갖는다.
Pending 상태가 아니면 Settled 상태라고 하며 resolved나 rejected 상태를 말한다.
Pending 상태에서는 fulfilled 또는 rejected 상태로 전이할 수 있으나, Promise가 한번 Settled 상태가 되고나면 다시 settled 될 수 없음.
한번 settled
Promise는 자신의 상태를 노출시키지 않음.
Promise를 생성하는 함수만이 Promise의 상태를 알고 resolve나 reject 콜백에 접근할 수 있음

Executor

promise의 executor는 반환값을 갖지 않음.
.then()으로 데이터를 전달하려면 resolve()나 reject()를 호출 시 인자로 전달해야한다.

resolve()

resolved 상태의 Promise를 반환

reject()

reject 상태의 Promise를 반환

race()

가장 먼저 resolve된 값 또는 가장 먼저 reject된 Promise의 이유를 가진 promise 반환

all()

promise로 이루어딘 배열 또는 반복자를 취하여 그 안에 들어있는 모든 promise가 resolve된 뒤의 값들 또는 가장먼저 reject된 promise의 이유를 가진 promise를 반환

Promise.prototype.then()

then에서는 성공적으로 이행된 프라미스를 실행
onfufilled or onReject === undefined
return : Promise

Promise.prototype.catch()

오류 발생 시 reject로 보낸 값을 파라미터로 받아옴, 에러를 받아온다.
return : reject로 받아온 값

Promise.prototype.finally()

성공 실패와 상관없이 수행.
인자를 받아오지 않음
JavaScript
복사

Promise.all

모든 작업이 완료될 때까지 기다림
전달값 : Promise가 실행된 후 결정된 값을 요소로 갖는 배열
Promise.all([promise1, promise2]).then((values) => console.log)
JavaScript
복사

Async / Await

async

함수 앞에 async를 붙이면 항상 promise를 반환
async function one() { return 1; // 아래 코드와 동일 // return Promise.resolve(1); }
JavaScript
복사

await

promise가 처리(settled)될 때까지 기다린 후 반환
async 함수 안에서만 동작
에러는 try ... catch를 사용해 잡는다.

Fetch

Fetch API
url을 통한 네트워크 요청
let url = '' fetch(url) .then((response) => response.json()) // 자체적으로 json() 메소드가 있어, 응답을 JSON 형태로 변환시켜서 다음 Promise로 전달합니다 .then((json) => console.log(json)) // 콘솔에 json을 출력합니다 .catch((error) => console.log(error)); // 에러가 발생한 경우, 에러를 띄웁니다
JavaScript
복사

예시

fs 모듈

Callback

const fs = require("fs"); const getDataFromFile = function (filePath, callback) { fs.readFile(filePath, "utf8", function (err, file) { if (err) { callback(err, null); } else { callback(null, file.toString()); } }); };
JavaScript
복사

Promise

const fs = require("fs"); const getDataFromFilePromise = filePath => { return new Promise((resolve, reject) => { fs.readFile(filePath, "utf8", function (err, file) { if (err) { reject(err); } else { resolve(file.toString()); } }); }); }; // getDataFromFilePromise('README.md') // .then(data => console.log(data)); module.exports = { getDataFromFilePromise };
JavaScript
복사

Promise Chaining

const fs = require("fs"); const path = require('path'); const user1Path = path.join(__dirname, 'files/user1.json'); const user2Path = path.join(__dirname, 'files/user2.json'); const getDataFromFilePromise = filePath => { return new Promise((resolve, reject) => { fs.readFile(filePath, "utf8", function (err, file) { if (err) { reject(err); } else { resolve(file.toString()); } }); }); }; const readAllUsersChaining = () => { return getDataFromFilePromise(user1Path) .then((user1) => { return getDataFromFilePromise(user2Path).then((user2) => { return '[' + user1 + ',' + user2 + ']'; }); }) .then((text) => JSON.parse(text)); };
JavaScript
복사

Promise.all

const fs = require("fs"); const path = require('path'); const user1Path = path.join(__dirname, 'files/user1.json'); const user2Path = path.join(__dirname, 'files/user2.json'); const getDataFromFilePromise = filePath => { return new Promise((resolve, reject) => { fs.readFile(filePath, "utf8", function (err, file) { if (err) { reject(err); } else { resolve(file.toString()); } }); }); }; const readAllUsers = () => { return Promise.all([ getDataFromFilePromise(user1Path), getDataFromFilePromise(user2Path), ]) .then(([user1, user2]) => { return '[' + user1 + ',' + user2 + ']'; }) .then((text) => JSON.parse(text)); };
JavaScript
복사

async / wait

const fs = require("fs"); const path = require('path'); const user1Path = path.join(__dirname, 'files/user1.json'); const user2Path = path.join(__dirname, 'files/user2.json'); const getDataFromFilePromise = filePath => { return new Promise((resolve, reject) => { fs.readFile(filePath, "utf8", function (err, file) { if (err) { reject(err); } else { resolve(file.toString()); } }); }); }; const readAllUsersAsyncAwait = async () => { let user1 = await getDataFromFilePromise(user1Path); let user2 = await getDataFromFilePromise(user2Path); let text = '[' + user1 + ',' + user2 + ']'; let json = JSON.parse(text); return json; };
JavaScript
복사

Fetch

Promise Chaining

const result = {}; return fetch(newsURL) .then(res => res.json()) .then((newsData) => { result['news'] = newsData['data']; return fetch(weatherURL) }) .then(res => res.json()) .then((weather) => { result['weather'] = weather; return result; }) .catch((error) => console.log(error)); /// function getNewsAndWeather() { const newsURL = 'http://localhost:5000/data/latestNews'; const weatherURL = 'http://localhost:5000/data/weather'; return fetch(newsURL) .then((resp) => resp.json()) .then((json1) => { return fetch(weatherURL) .then((resp) => resp.json()) .then((json2) => { return { news: json1.data, weather: json2, }; }); }); }
JavaScript
복사

Promise All

function getNewsAndWeatherAll() { const newsURL = 'http://localhost:5000/data/latestNews'; const weatherURL = 'http://localhost:5000/data/weather'; return Promise.all([fetch(newsURL), fetch(weatherURL)]) .then(([newsResponse, weatherResponse]) => { return Promise.all([newsResponse.json(), weatherResponse.json()]); }) .then(([json1, json2]) => { return { news: json1.data, weather: json2, }; }); }
JavaScript
복사

async await

async function getNewsAndWeatherAsync() { const newsURL = 'http://localhost:5000/data/latestNews'; const weatherURL = 'http://localhost:5000/data/weather'; const json1 = await fetch(newsURL).then((resp) => resp.json()); const json2 = await fetch(weatherURL).then((resp) => resp.json()); return { news: json1.data, weather: json2, }; }
JavaScript
복사

Reference

A. MDN

웹의 최신 기술을 전반적으로 학습할 때, 지속적으로 읽을 수 있는 좋은 자료가 있습니다.

D. 그밖에...