(출처 : https://www.udemy.com/course/100-2022-web-development/ )
0.
자바스크립트는 프론트앤드와 백엔드를 다 처리가능한 유용한 언어이다.
그렇기에 JS에 대해 더 Dive Deep해보고 가는게 좋을 것이다.
---
1. JS의 함수와 관련된 개념을 먼저 한번 보고
2. 배열(Array)에 대해 알아보자.
3. Reference 와 Primitive Value에 대해서 알아보고
4. Asynchronous Code, 비동기 코드에 대해서 배워보도록 하자.
---
1. 함수의 디폴트값 설정
입력받는 파마리터에 디폴트 값을 설정해서
파라미터를 입력받지 않아도 기본적으로 출력이 되도록 하자.
function greetUser(userName){
console.log('Hi ' + userName + '!');
}
greetUser('Max');
greetUser();
이렇게 하면, 에러는 발생하지 않고
Hi Max!
Hi undefined!
라고 출력이 된다. undefined라는 특수값이 출력되는 것이다.
function greetUser(userName = 'user'){
console.log('Hi ' + userName + '!');
}
greetUser('Max');
greetUser();
이런식으로 userName = 'user' 와 같이 등호를 이용해 디폴트 값을 설정해주면
Hi Max!
Hi user!
로 출력되는 것을 볼 수 있다.
function greetUser(greetingPrefix, userName){
console.log(greetingPrefix + 'Hi ' + userName + '!');
}
greetUser('Max');
greetUser();
또한, 이러한 함수는 여러개의 매개변수를 요구하기 마련인데,
이 경우 디폴트 값이 없는 Non-optional 파라미터 뒤에 Optional 파라미터를 넣어야한다.
디폴트 값이 있는 Optional 파라미터는 상관이 없는데,
Non-optional 값은 필수적으로 값을 전달받아야 하며, 아니면 undefined라는 값으로 들어와 문제를 일으키기 때문이다.
---
2.
아래와 같은 함수를 정의하자.
function sumUp(num1, num2, num3){
return num1 + num2 + num3
}
입력받은 파라미터들의 합을 리턴하는 함수이다.
이 함수에는 한계점이 있는데
세개의 숫자를 정확히 넣어야 한다는 점이다.
두개의 숫자는 넣을 수 없다는 점.
여기서
console.log(sumUp(1,2));
를 수행하면,
NaN을 출력하게 된다.
자바스크립트에는 Special Value/ 특수값들이 존재한다.
undefined : "no value"라는 뜻이며, 변수가 값을 할당받지 못했을 때 사용된다.
null : "no value"라는 뜻이며, 사용자가 의도적으로 값을 할당하지 않을 때 사용된다.
NaN " Not a Number라는 뜻이며, 숫자가 입력되어야 하는데 숫자로 인식되지 않는 다른 값이 입력되었다는 뜻이다.
function sumUp(num1, num2, num3=0){
return num1 + num2 + num3
}
그래서 이러한 문제를 default값을 0으로 설정해서 문제를 해결할 수도 있고.
function sumUp(numbers){
let result = 0;
for (const number of numbers){
result += number;
}
return result;
}
console.log(sumUp([1,5,10,11]))
와 같이 배열을 사용해서 문제를 해결할 수 있을 것이다.
다만 자바스크립트에서는 이러한 경우에 대해서
간단하게 함수를 처리할 수 있는 기능이 내장되어있다.
굳이 배열로 wrap할 필요가 없도록 말이다.
function sumUp(...numbers){
let result = 0;
for (const number of numbers){
result += number; // result = result + number
}
result result ;
}
console.log(sunUp([1,5,10,11,20,31]));
위와 같이 점 세개를 넣은 ...연산자를 통해서
이 함수는 매개변수(Argument)에 대해서 제한된 길이 두지 않고 입력받는 기능을 제공한다.
자체적으로 자바스크립트가 콤마 ','로 구분하여 numbers라는 배열에 저장된다.
다만 이런식으로 쓰리닷연산자를 사용할 경우
역으로 이미 리스트로 wrap된 자료를 입력하고 싶은 경우도 발생하게 된다.
위의 함수에다가 sumUp( [1,5,10,11,20,31] ); 과 같은 입력을 줘버리면,
해당 배열을 그대로 반환한다.
function sumUp(...numbers){
let result = 0;
for (const number of numbers){
result += number; // result = result + number
}
return result ;
}
const inputNumbers = [1,5,10,11,20,31];
console.log(sumUp(...inputNumbers));
그러한 경우, 매개변수(Parameter)값에서 ... 연산자를 사용하면된다.
이 경우 쓰리닷 연산자는 스프레드(Spread) 연산자, 즉 배열의 원소들을 분산해주는 기능을 수행해준다.
---
3.
JS에서는 함수도 결국 객체(Object)이다.
우리가 강의 초반에 객체라고 배웠던 것은
const person = {
name : 'Doe',
age : 32,
};
와 같이 선언하고 person.name 에 바로 접근가능한 자료구조 였다.
하지만 node에서 위의 sumUp함수를 사용해서
console.log(sumUp)을 출력해보면
[Function: sumUp]
이 나오는 것을 알 수 있다.
브라우저에서
실행해볼 경우 함수가 객체인것을 더 자세히 알 수 있다.
빈브라우저의 콘솔창에서
function add(num1, num2) { return num1 + num2 } 를 입력하고
console.dir(add)을 수행하면
해당 함수의 구조와 속성을 확인할 수 있다.
이러한 것들은 수동으로 추가된 속성이 아니며
자동으로 추론된(Inferred) 속성들이다.
왜 이런 속성들을 수동으로 설정하지 않아도 되는걸 알아야 하는가?
왜냐하면 이것은 우리가 이미 많이 겪어봤던 현상이기 때문이다.
const app = express()
와 같이 외부 라이브러리를 임포트해왔었다.
그리고 이를 이용하여 app.use(express.urlencoded());와 같이
추가적인 기능들을 직접 구현하지 않고도 사용할 수 있었다.
또한 원한다면
sumUp.someProperty = 3; 과 같이 추가적인 속성을 설정할수도 있다.
이러한 작업들이 가능한 이유는 함수가 객체이기 때문이라는 것이다.
---
4.
비동기코드 Asynchronous Code
이번 JS심화 강의의 핵심인 비공기코드에 관해서 알아보도록 하자.
이전 코드에서 사용했던 readFile 예제를 조금 변형해서 아래의 코드를 async.js에 작성해주자.
const fs = require('fs');
function readFile() {
let fileData;
fileData = fs.readFileSync('data.txt');
console.log(fileData);
console.log('Hi there!');
}
readFile();
해당 js파일과 동일한 디렉토리에
data.txt를 만들어 "This works - data from the text file!" 이라는 내용을 저장해주고,
Async하게 해당 파일을 읽어보자.
위와 같이 파싱이 안되어서 버퍼가 통째로 전달된 것을 볼 수 있다.
자바스크립트의 빌트인 메소드인
fileData.toString();
을 통해서 JS에 적합한 string 형태로 바꿔서 다시 파일을 실행해보면
위와 같이 정상적으로 파일을 읽어들이는 것을 알 수 있다.
이전에는 json파일을 파싱했기 때문에 toString은 처음 써보긴했지만
크게 새로운 개념은 아니다.
주목하고 싶은 점은, 파일을 읽어들이는 과정에서 사용된
readFileSync라는 함수이다.
파일(File)을 읽(read)으니까 readFile은 이해가 되는데
Sync는 어디서 붙었을까?
왜냐하면
JS에서 readFileSync 메소드를 통해
파일을 읽는 과정을 Synchronous(동기적)으로 수행했기 떄문이다.
그리고 백엔드 성능의 만악의 근원인 DB읽기 과정에서
이러한 동기적 읽기는 많은 시간이 걸릴수 있다.
파일을 읽고 데이터를 읽는 과정은 대개 많은 시간이 걸리는 과정이다.
그리고 웹개발의 과정에서는, 거대한 파일을 읽는 것이 일상적인 일이고
이러한 파일을 읽는 과정에서 blocked 상태에 빠지는 것도 일상적인 일이다.
fileRead뿐만 아니라 거대한 프로세스들을 실행하는 과정에서는 그 작업이 끝나기 까지 시간이 걸리고
이를 순차적이고 Synchronous하게 수행하면 성능이 저하되기 마련이다.
---
그래서 Node.js에서는 비동기적인 작업을 통해 Non-blocking I/O를 수행하는 것을 철학을 반영하여
동기적인 방식의 readFileSync보다
비동기적인 방식의 readFile 메소드를 더 짧게 명명한 감이 있다. 물론 둘다 중요한 메소드다.
const fs = require('fs');
function readFile() {
let fileData;
fileData = fs.readFile('data.txt', function(){
console.log('File parsing done!')// Callback function
});
console.log(fileData.toString());
console.log('Hi there!');
}
readFile();
비동기적인 readFile 메소드는 readFileSync와 다르게
함수가 완료된 후 수행될 '콜백 함수'를 두번째 파라미터로 요구한다.
그래서 위 코드를 그대로 수행하게 될 경우 다음과 같은 결과를 얻는다.
오류가 발생하였다.
비동기적으로 readFile을 수행하였으나
파일을 다 읽고 fileData에 데이터를 저장하기 전에
undefined상태에서 이녀석을 console.log와 toString으로 처리하려고 시도했기 때문에
위와 같은 오류가 발생한다.
---
const fs = require('fs');
function readFile() {
let fileData;
fileData = fs.readFile('data.txt', function(error, fileData){ // function은 자동으로 error객체를 전달받음
// Callback function
console.log('File parsing done!')
console.log(fileData.toString());
});
console.log('Hi there!');
}
다시 돌아가서, 콜백함수는 파라미터로 error객체를 전달받는다.
error가 일어나지 않고 정상적으로 작업이 완료가 되었다면, error는 undefined로 남겠지만
파일을 제대로 읽지 못했다면, error 객체를 통해 오류에 대한 정보를 확인할 수 있다.
위의 코드를 async.js에 저장하고 실행하면, 다음과 같은 결과를 얻을 수 있다.
코드상으로는 뒤에서 작성된 console.log('Hi there!)가 먼저 수행되고
콜백함수인 'File parsing done!'과 'This works~ ... '가 출력되는 것을 볼 수 있다.
---
5.
프로미스 Promise
(*뒤에 배울 Async/Await을 위한 기초개념에 가깝다.)
앞선 강의에서 asynchronous 코드와 readFile에 대해서 배워보았다.
앞선 비동기함수의 개념과 콜백함수만으로도 충분히 많은 내용을 함의하고 있으나
실전에 들어가면, 비동기 함수속에 또 비동기함수를 *동시에 연속적으로 수행하고
비동기 함수 속의 비동기 함수에 대한 콜백루틴을 수행하면서
콜백함수를 넣어주는 것만으로는 유지보수하기 힘든 코드가 발생하는 상황을 마주하게 된다.
이를 콜백지옥Callback Hell이라고 한다
스파게티 코드의 if-else 지옥같이 말이다.
const fs = require('fs');
function readFile() {
let fileData;
fileData = fs.readFile('data.txt', function(error, fileData){
console.log('File parsing done!')// Callback function
console.log(fileData.toString());
// start another async tast cf.sending data to db
});
그래서 이러한 중첩된 비동기함수와 콜백함수를 관리하는
빌트인 패키지와 써드파티 패키지를 알아놓을 필요가 있다.
그 중에 중요한 개념이 프로미스Promises이다.
현실에서 Promise란, 미래에 어떠한 일을 할 것을 맹세하는 것이다.
채무자가 돈을 지금 빌리고, 미래에 갚을 것을 Promise(맹세)하듯이,
JS에서도 프라미를 이용해 Async함수를 다룰 수 있다.
프로미스는
브라우저와 노드JS의 자바스크립트에서 정의된
비동기함수에 대한 표준화된 객체와 메소드이며
프로미스가 제대로 수행된 이행(Fulfilled)상태가 되면 then 메소드를,
제대로 수행되지 않고 거부(Rejected)상태가 되면 catch 메소드를
프로미스와 관계없이 어쨌든 수행할 fiannly 메소드를
수행하도록 하여
비동기함수의 콜백지옥을 타파할 수 있다.
그리고 차후에 배울
async/await 문법의 원형이 되는 개념이기도 하다.
---
위에서 텍스트 파일을 읽어오는 작업을 수행하는 코드를
promise문법에 맞게 작성하면 다음과 같다.
const fs = require('fs/promises');
function readFile() {
fs.readFile('data.txt').then(function(fileData){
// 성공적으로 파일을 읽었을때 수행
console.log('File parsing Done!');
console.log(fileData.toString());
});
console.log('Hi there!');
}
readFile();
---
아래의 코드를 통해
비동기적함수가 순차적으로 수행되어야 하는, 즉 call-back지옥을 잘 다루는
프로미스의 유용성을 확인할 수 있다.
function storeData(){
fs.readFile('input-data.csv', function (error, data){
const cleanedData = cleanData(data);
storeDatainDatabase(cleanedData, function(error, result){
if (result.changedData){
confirmDataChange(function(error, done){
if (!error && done){
res.render('success');
}
});
}
});
}
}
const fs = require('fs/promises');
fs.readFile('input-data.csv')
.then(function(data) {
const cleanedData = cleanData(data);
return storeDataInDatabase(cleanedData);
})
.then(function(result){
if (result.changeData){
return comfirmDataChange();
}
})
.then(function(done){
if(done){
resizeBy.render('success');
}
})
---
catch 와 finally는
비동기함수에서의 에러제어방식이다.
아래와 같이 catch를 통해 에러를 제어하는 코드를 참조하자.
const fs = require('fs/promises');
function readFile() {
fs.readFile('data.txt')
.then(function(fileData){
// 성공적으로 파일을 읽었을때 수행
console.log('File parsing Done!');
console.log(fileData.toString());
})
.then(function (){})
.catch(function (error) {
console.log(error);
});
console.log('Hi there!');
}
---
6. Async/Await 키워드
(1)프로미스는 중첩된 비동기함수를 다루기 위한 좋은 방안이지만
여전히 then이나 catch도 상당히 길고 복잡한 편이며
프로미스를 사용함에도 여전히 앞서 비동기적으로 수행하고 있는 작업이 완료된 이후에
순서에 맞춰 수행하고 싶은 작업들을 다루는 것에 불편하게 느껴질 것이다.
(2)또한 개발을 하다보면
readFile같이 비동기 기능을 제공하는 함수가 그렇게 많은 것도 아니고
대안으로 readFileSync같은 동기기능도 같이 제공해주는 경우는 많지 않다.
-> 그래서 async와 await 문법을 이용해서
이러한 문제점을 해결할 수 있다.
---
앞서 프로미스를 이용해 만든 비동기함수로 텍스트 파일을 읽는 예제 코드이다.
const fs = require('fs/promises');
function readFile() {
let fileData;
fs.readFile('data.txt')
.then(function(fileData){
// 성공적으로 파일을 읽었을때
console.log('File parsing Done!');
console.log(fileData.toString());
})
}
readFile();
위의 코드에서
function readFile() { ... } 를
async function readFile() {...}로 바꾸어주고
fileData에 await fs.readFile('data.txt');를 넣어주자.
const fs = require('fs/promises');
async function readFile() { //
let fileData
fileData = await fs.readFile('data.txt');
...
이 함수는 이제 async 키워드로 인해 자동으로 프로미스를 반환하게 된다.
그리고 async키워드는 그 함수 내부에서 await 키워드를 사용할 수 있도록 해준다.
이 await 키워드를 통해, 우리는 다음과 같이 프로미스의 .then 문법을 대체해 줄 수 있다.
const fs = require('fs/promises');
async function readFile() {
let fileData;
fileData = await fs.readFile('data.txt')
// 성공적으로 파일을 읽었을때
console.log('File parsing Done!');
console.log(fileData.toString());
}
readFile();
fileData = await fs.readFile('data.txt') 쪽을 떼어보면
뭔가 기존에 사용해왔던 동기함수처럼 보이고, 결국 처음으로 돌아온것같은 기분이 들것이다.
하지만 역으로 말하자면 기존의 문법을 이용해 비동기함수를 다루는 방법을 배운셈이 된다.
await를 통해서 fs.readFile은 자동적으로 프로미스를 반환하여 fileData에 저장하고,
이후의 작업들은
fileData = await fs.readFile('data.txt') 작업이 끝나기 전까지 수행되지 않는다.
프로미스의 then 블록에 묶여있는것과 똑같은 작업을 수행하는 것으로 생각할 수 있다.
const fs = require('fs/promises');
async function readFile() {
let fileData;
try{
fileData = await fs.readFile('data.txt');
} catch (error){
console.log(error);
}
// 성공적으로 파일을 읽었을때
console.log('File parsing Done!');
console.log(fileData.toString());
// return anotherAsyncOperation
console.log('Hi there!');
}
readFile();
또한, 기존의 try-catch문법을 이용해서
프로미스의 catch블록또한 위와 같이 나타낼수 있다.
여기까지가 Async코드였다.
겉으로 보기에는 Synchronous 코드와 다를게 없지만,
Async와 Await를 이용하면
비동기함수를 동시에 시작하는 수고를 겪지 않아도되고
콜백함수를 중첩시키면서 헷갈려할 필요도 없게 된다.
--
그래서 요점만 정리하자면
async/await을 이용하여 아래와 같은 비동기 함수를 정의할 수 있겠다.
const fs = require('fs/promises');
async function readFile() {
let fileData;
// 비동기로 파일 읽기를 시작하고 Promise를 저장
const fileReadPromise = fs.readFile('data.txt');
// 파일 읽기를 기다리지 않고 즉시 실행
console.log('Hi there!');
try {
// 여기서 비로소 파일 읽기 Promise의 완료를 기다림
fileData = await fileReadPromise;
console.log('File parsing Done!');
console.log(fileData.toString());
} catch (error) {
console.log(error);
}
}
readFile();
'지식이 늘었다 > 웹개발' 카테고리의 다른 글
DB (2) MySQL 설치 (0) | 2024.12.04 |
---|---|
DB (1) 데이터베이스 개념, SQL과 NoSQL (0) | 2024.12.03 |
NodeJS (4) EJS 템플릿 (0) | 2024.12.01 |
NodeJS (3) ExpressJS를 활용한 유저 데이터 저장 및 반환 (0) | 2024.12.01 |
NodeJS (2) NPM, ExpressJS 튜토리얼 (0) | 2024.11.30 |