// 동기적인 실행 예시
function B() {
console.log('hi');
}
function A(callback) {
callback(); // callback === B
}
A(B);
사용 예제
[1, 2, 3].map(function(ele, idx) {
return ele * ele;
});
document.querySelector('#btn').addEventListener('click', function(e) {
console.log('button clicked');
});
function handleClick() {
console.log('button clicked');
};
document.querySelector('#btn').onclick = handleClick(); // 이 경우에는 함수에 return값이 없기 때문에 undefined를 연결한 것과 같음. 괄호가 없어야 함수 자체를 연결하는 것이다.
동기적(Synchronous) : 시작 시점과 완료 시점이 같은 상황
비동기적(Asynchronous) : 시작 시점과 완료 시점이 같지 않은 상황.
예시 1 :
카페에서 커피를 주문하려고 줄을 서있는 상황!
먼저 온 사람이 주문한 커피를 받을 때 까지 다음 사람은 주문조차 할 수 없다고 한다.(blocking) ➡️ 동기적이다.
커피 주문을 언제든지 받을 수 있고 커피가 완성되는 즉시 커피를 진동벨로 알려 제공한다.(non-blocking) ➡️ 비동기적이다.
예시 2 :
전화 문자 하던 일을 멈추고 받아야 한다.(blocking) 확인 후, 나중에 답장할 수 있다.(non-blocking) 요청에 대한 결과가 동시에 일어난다.(Synchronous) 요청에 대한 결과가 동시에 일어나지 않는다.(Asynchronous)
JavaScript is synchronous.
자바스크립트는 기본적으로 한번에 한 줄씩 실행되므로 동기적인 처리 언어이다.
setTimeout, 이벤트 리스너, ajax 함수를 쓰면 비동기적인 처리도 가능하다.
JavaScript의 비동기적 실행(Asynchronous execution)이라는 개념은 웹 개발에서 유용하게 사용된다. 특히, 아래 작업은 비동기적으로 작동되어야 효율적이다.
Node.js를 만든 개발자도 위 대안이 합리적이라고 생각하여 Node.js는 non-blocking하고 비동기적(asynchronous)으로 작동하는 런타임으로 개발되었다.
function waitSync(ms) {
var start = Date.now();
var now = start;
while(now - start < ms) {
now = Date.now();
}
} // 현재 시각과 시작 시각을 비교하며 ms 범위 내에서 무한 루프를 도는 blocking 함수
function drink(person, coffee) {
console.log(person + '는 ' + coffee + '를 마십니다');
}
function orderCoffeeSync(coffee) {
console.log(coffee + '가 접수되었습니다');
waitSync(2000); // 2초 동안 blocking
return coffee;
}
let customers = [{
name: 'Steve',
request: '카페라떼'
}, {
name: 'John',
request: '아메리카노'
}];
// call synchronously
customers.forEach(function(customer) {
let coffee = orderCoffeeSync(customer.request);
drink(customer.name, coffee);
});
/*---------------------
카페라떼가 접수되었습니다
Steve는 카페라떼를 마십니다
아메리카노가 접수되었습니다
John는 아메리카노를 마십니다
-----------------------*/
function waitAsync(callback, ms) {
setTimeout(callback, ms); // 특정 시간 이후에 callback 함수가 실행되게끔 하는 브라우저 내장 기능
}
function drink(person, coffee) {
console.log(person + '는 ' + coffee + '를 마십니다');
}
function orderCoffeeSync(coffee) {
console.log(coffee + '가 접수되었습니다');
waitSync(2000);
return coffee;
}
let customers = [{
name: 'Steve',
request: '카페라떼'
}, {
name: 'John',
request: '아메리카노'
}];
function orderCoffeeAsync(menu, callback) {
console.log(menu + '가 접수되었습니다');
waitAsync(function() {
callback(menu);
}, 2000);
}
// call asynchronously
customers.forEach(function(customer) {
// 콜백 패턴
orderCoffeeAsync(customer.request, function(coffee) {
drink(customer.name, coffee);
});
});
// +) 이벤트 등록 패턴
orderCoffeeAsync(request).onready = function(response) {
drink(response);
}
/*--------------------
카페라떼가 접수되었습니다
아메리카노가 접수되었습니다
Steve는 카페라떼를 마십니다
John는 아메리카노를 마십니다
-----------------------*/
const printString = (string) => {
setTimeout(
() => {
console.log(string)
},
Math.floor(Math.random() * 100) + 1 // random 시간으로 기다리기 때문에 뭐가 먼저 끝날지 모름
)
}
const printAll = () => {
printString("A")
printString("B")
printString("C")
}
printAll() // 매번 순서가 다르게 나옴
const printString = (string, callback) => { // 콜백 함수를 받음
setTimeout(
() => {
console.log(string)
callback() // 콜백 함수 실행
},
Math.floor(Math.random() * 100) + 1
)
}
const printAll = () => {
printString("A", () => {
printString("B", () => {
printString("C", () => {})
})
})
}
printAll() // 순서대로 A, B, C 출력
somethingGonnaHappen = ((err, data) => {
if(err) {
console.log('ERR!!');
return;
}
return data;
})
콜백..유용하지만? 연속해서 사용한다면 지옥이 펼쳐질 것이다..
This is Callback HELL..
ES6에서는 비동기 처리를 위한 패턴으로 프로미스(Promise)를 도입했다.
콜백과 동일한 동작이지만, 코드 가독성이 향상된다.
.then()으로 다음 task를 동작할 수 있다.
어느 곳에서 에러가 나더라도 .catch()로 에러를 잡을 수 있다. (콜백의 경우에는 콜백 처리 시 마다 에러 핸들링을 해줘야 함..)
프로미스의 3가지 상태(states)
pending(대기)
: 비동기 처리가 수행되지 않은 Promise 객체가 생성만 된 상태fulfilled(이행)
: resolve 콜백함수가 호출되어 비동기 처리가 실행 된 상태rejected(실패)
: reject 콜백함수가 호출되어 비동기 처리가 실행 된 상태, 에러를 catch로 잡아주면 rejected 상태에서 fulfilled 상태로 바꿔줄 수 있다.const printString = (string) => {
return new Promise((resolve, reject) => { // Promise화 된 함수
setTimeout(
() => {
console.log(string)
callback() // 콜백 함수 실행
},
Math.floor(Math.random() * 100) + 1
)
})
}
const printAll = () => {
printString("A").then(() => {
return printString("B")
})
.then(() => {
return printString("C")
})
}
printAll() // 순서대로 A, B, C 출력
Promise도 HELL이 일어날 수 있기 때문에 return을 통해 다음 비동기로 넘기는 테크닉(Promise Chaining)이 필요하다.
// 코드의 일부만 가져온 예시, 눈으로 구조 익히는 용도!
// promise를 리턴하는 함수
const promiseConstructor = filePath => {
return new Promise((resolve, reject) => { // resolve, reject는 콜백이자 exacutor 함수
fs.readFile(filePath, 'utf-8', (err, data) => {
if(err) reject(err); // 에러 발생 시 -> catch를 따라감
else resolve(data); // 정상 수행 시 -> then을 따라감
});
})
};
// Promise Chaining 예시
const promiseChaining = () => {
return promiseConstructor(pathParam1).then((user1) => {
return promiseConstructor(pathParam2).then((user2) => {
return [JSON.parse(user1), JSON.parse(user2)] // user1과 user2의 데이터를 JSON으로 파싱
})
})
promiseChaining();
function A() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('A') }, 300)
})
}
function B() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('B') }, 200)
})
}
function C() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('C') }, 100)
})
}
const printAll = async() => {
const printA = await A()
console.log(printA);
const printB = await B()
console.log(printB);
const printC = await C()
console.log(printC)
}
printAll()
실행 시간을 300, 200, 100 순으로 부여해도 await 키워드는 Promise가 처리될 때까지 기다리기 때문에 차례대로 실행된다.
setTimeout(callback, millisecond)
: 일정 시간 후에 함수를 실행, 임의의 타이머 ID를 returnsetTimeout(function () {
console.log('1초 후 실행');
}, 1000);
clearTimeout(timerId)
: setTimeout 타이머를 종료, return 값 없음const timer = setTimeout(function () {
console.log('10초 후 실행');
}, 10000);
clearTimeout(timer); // setTimeout이 종료됨.
setInterval(callback, millisecond)
: 일정 시간의 간격을 가지고 함수를 반복적으로 실행, 임의의 타이머 ID를 returnsetInterval(function () {
console.log('1초마다 실행');
}, 1000);
clearInterval(timerId)
: setInterval 타이머를 종료const timer = setInterval(function () {
console.log('1초마다 실행');
}, 1000);
clearInterval(timer);
// setInterval이 종료됨.
Node js is an asynchronous event-driven JavaScript runtime.
정의 : Node.js는 비동기 이벤트 기반 JavaScript 런타임이며, 확장 가능한 네트워크 애플리케이션을 빌드하도록 설계되었다.
runtime
: 런타임은 프로그램이 실행되고 있는 때 존재하는 곳, 즉, 프로그래밍 언어가 구동되는 환경이다.
readFile()
: 파일을 읽을 때 사용하는 메서드writeFile()
: 파일을 저장할 때 사용하는 메서드브라우저에서 다른 파일을 불러올 때
<script>
를 사용했듯이, Node.js에서는 코드 가장 상단에require
를 사용하여 다른 모듈을 불러온다!
const fs = require('fs'); // 파일 시스템 모듈을 불러옴
const dns = require('dns'); // DNS 모듈을 불러옴
// 이후 fs.readFile 메서드 등을 사용할 수 있음
// 서드 파티 모듈을 다운로드 하기 위해서 npm을 사용한다.
npm install underscore
const _ = require('underscore'); // underscore 모듈을 불러옴
내장 모듈 공식 문서
공식 문서를 애용하자!! (번역기 바로 돌리지말고 영어로 일단 이해하려고 노력하기)
fs.readFile은 비동기적으로 파일 내용 전체를 읽는 모듈이다.
fs.readFile(path[, options], callback)
path
<string> | <Buffer> | <URL> | <integer>
path에는 파일 이름을 전달인자로 받습니다. 네 가지 종류의 타입을 넘길 수 있지만 일반적으로 문자열의 타입을 받습니다.
option
<Object> | <string>
대괄호로 감싼 두 번째 전달인자 options는 넣을 수도 있고, 넣지 않을 수도 있습니다. 대괄호는 선택적 전달인자를 의미합니다.
options는 문자열 또는 객체 형태로 받을 수 있습니다. 문자열로 전달할 경우 인코딩을 받습니다. 밑의 예제에서는 'utf8' 을 두 번째 전달인자로 받는 것을 확인할 수 있습니다.
callback
<Function>
err
<Error> | <AggregateError>data
<string> | <Buffer>콜백 함수를 전달합니다. 파일을 읽고 난 후에 비동기적으로 실행되는 함수입니다.
콜백 함수에는 두 가지 매개변수가 존재합니다. 에러가 발생하지 않으면 err는 null 이 되며, data에 문자열이나 Buffer 라는 객체가 전달됩니다. data는 파일의 내용입니다.
인코딩이 지정되지 않으면 원시 버퍼가 반환됩니다.
fs.readFile('test.txt', 'utf8', (err, data) => {
if (err) {
throw err; // 에러를 던던던던 던져!
}
console.log(data);
});
원격 URL로부터 정보를 받아옴 -> 특정 DOM 엘리먼트를 업데이트
let url = "https://v1.nocodeapi.com/codestates/google_sheets/YbFMAAgOPgIwEXUU?tabId=최신뉴스";
fetch(url)
.then((response) => response.json())
.then((json) => console.log(json))
.catch((error) => console.log(error));
스스로에게 질문하는 시간!! 말로 설명해보자.
| 참고자료 |