이미지 출처: codestates urclass
동기(Synchronous): Server가 Client의 요청을 받고 일 처리를 위해 데이터 베이스를 가져오거나 이를 가공하는 동안 Client는 아무것도 하지 않고 요청을 기다리다가 Server로부터 응답(Response)이 온 순간부터 작업을 실행하게 되는 것을 의미한다.
비동기(Asynchronous): Server가 Client의 요청을 받고 일 처리를 위해 데이터 베이스를 가져오거나 이를 가공하는 것은 동기적인 처리 방식과 동일하지만, 비동기는 Server가 일 처리를 하는 동안 Client 또한 하던 작업을 진행하거나 다른 요청이 들어오면 그 요청을 계속 수행하다가 Server로부터 요청한 응답이 오면 그 응답을 가지고 다시 작업을 실행하는 것을 의미한다. 즉 동기적인 처리 방식은 Server에 요청을 해놓고 그 요청만을 기다리는 것이지만, 비동기적인 처리 방식은 Server에 요청을 해놓고 다른 작업을 하며 요청을 기다리는 것이다.
Client: 서버로 접속하는 컴퓨터
Server: 서비스, 리소스 등을 제공하는 컴퓨터(ex. 웹 서버, 게임 서버 등)
동기적인 처리 방식은 한 가지 작업 수행 후 다른 작업을 할 수 있지만, 비동기적인 처리 방식은 모든 작업을 동시에 수행할 수 있기 때문에 더 효율적인 작업 방식이라 할 수 있다. 백그라운드 실행, 로딩 창 등과 같은 부분들이 실생활에서의 비동기 작업이라 할 수 있다. 인터넷에서의 요청, 큰 용량의 파일을 로딩하는 등의 작업을 하기 위해서는 비동기 처리가 반드시 필요하다.
콜백(Callback)은 비동기(Async)의 순서를 제어할 수 있는 방법이다. 즉 어떤 함수의 인자로 Callback을 받아 실행함으로써 비동기 함수의 실행 순서를 제어할 수 있다.
const printString = (string, callback) => {
setTimeout(
() => {
console.log(string)
callback()
},
Math.floor(Math.random() * 100) + 1
)
}
const printAll = () => {
printString("A", () => {
printString("B", () => {
printString("C", () => {})
})
})
}
printAll()
그러나 콜백이 순차적으로 이루어지면 코드의 가독성이 떨어지고 관리가 어려워지는 콜백 지옥(Callback Hell)에 빠질 수 있다는 단점이 있다.
코드의 가독성이 떨어지고 관리가 어려운 콜백의 단점을 보완하기 위한 방법으로, 일종의 Promise Class라고 볼 수 있다. new Promise를 통해 인스턴스를 만들고 Resolve나 Reject라는 명령어를 통해서 다음 액션으로 넘어가거나 에러를 핸들링 할 수 있다.
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(
() => {
console.log(string)
resolve()
},
Math.floor(Math.random() * 100) + 1
)
})
}
const printAll = () => {
printString("A")
.then(() => {
return printString("B")
})
.then(() => {
return printString("C")
})
}
printAll()
함수를 만들 때 Callback을 인자로 받지 않고 새로운 Promise 인스턴스를 리턴한다. 그리고 새로운 Promise는 Resolve와 Reject를 인자로 받는 Callback을 실행하게 된다. Callback이 아닌 .then으로 명령을 이어나갈 수 있다. Chaining 과정에서 어디에서 에러가 발생하더라도 마지막에 .catch를 사용하여 처리할 수 있다. 그러나 return처리(Promise Chaining)를 잘 해주지 않으면 Promise 또한 Callback처럼 Promise Hell이 발생할 수 있다.
Promise는 new Promise()로 Promise를 생성하고 종료될 때까지 대기(Pending), 이행(Fulfilled), 거부(Rejected) 3가지 상태를 갖는다. 여기서 상태란 Promise의 처리 과정을 의미한다.
Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
Rejected(거부) : 비동기 처리가 실패하거나 오류가 발생한 상태
여러 개의 Promise를 처리할 때는 Promise.all을 사용할 수 있다.(아래는 MDN예제)
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 100);
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
Promise와 작동 원리는 같지만, Await 키워드를 사용하면 비동기 함수들을 동기적인 것처럼 쓸 수 있다. 함수 앞에 Async를 써줘야한다.
function gotoCodestates() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('1. go to codestates') }, Math.floor(Math.random() * 100) + 1)
})
}
function sitAndCode() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('2. sit and code') }, Math.floor(Math.random() * 100) + 1)
})
}
function eatLunch() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('3. eat lunch') }, Math.floor(Math.random() * 100) + 1)
})
}
function goToBed() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('4. goToBed') }, Math.floor(Math.random() * 100) + 1)
})
}
const result = async () => {
const one = await gotoCodestates();
console.log(one)
const two = await sitAndCode();
console.log(two)
const three = await eatLunch();
console.log(three)
const four = await goToBed();
console.log(four)
}
result();
브라우저에서 사용할 수 있는 비동기 흐름은 타이머 혹은 DOM 이벤트와 관련된 상황으로 다소 한정적이지만, node.js의 경우 많은 부분의 API가 비동기로 작성되어 있다. node.js 소개 문서의 첫 단락은 "비동기 이벤트 기반 자바스크립트 런타임(an asynchronous event-driven JavaScript runtime)"이라는 정의로부터 시작한다.
건축으로부터 비롯된 모듈(Module)이라는 단어는, 어떤 기능을 떼서 조립할 수 있는 형태로 만든 부분을 의미한다. fs(File System) 모듈은 파일을 읽거나 저장하는 등의 일을 할 수 있게 돕는 node.js의 한 부분이다.
파일을 읽을 때에는 readFile, 저장할 때는 writeFile 메소드를 사용한다. 모듈을 사용하기 위해서는 이를 불러오는 과정이 필요하다.
브라우저에서는 script태그를 이용했다면,
<script src="불러오고싶은_스크립트.js"></script>
node.js 에서는 자바스크립트 코드 가장 상단에 require 구문을 이용하는 것으로 시작한다.
const fs = require('fs') // 파일 시스템 모듈을 불러옵니다
const dns = require('dns') // DNS 모듈을 불러옵니다
// 이제 fs.readFile 메소드 등을 사용할 수 있습니다!
3rd-party라는 용어는 프로그래밍 세계에서, 공식적으로 제공하는 것이 아닌 다른 모든 "제3자"의 것을 의미한다. node.js 문서에 존재하지 않는 것은 node.js에서 공식적으로 제공하지 않는 것이므로 나머지는 전부 3rd-party라고 할 수 있다. 예를 들어, node.js에서 underscore를 사용한다고 가정한다면, 모듈을 다운로드받기 위해 npm을 이용해야 한다.
먼저 npm을 이용하여 underscore를 설치하고,
$ npm install underscore
node_modules에 underscore가 설치되면, node.js 내장 모듈을 사용하듯 require구문을 통해 underscore를 사용할 수 있다.
const _ = require('underscore');