•
위 예시를 읽고, 아래 개념을 대략적으로 이해할 수 있다.
◦
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. 그밖에...
•
[Advanced] Understanding Asynchronous JavaScript