Node.js에서 fs 모듈을 활용하여 폴더를 생성하는 방법을 예제를 통해 정리한다.
example.js
"use strict"; // A
const fs = require('fs');
const checkDir = (path, callback) => {
fs.stat(path, (err, stats) => {
if (err && err.code === 'ENOENT')
return callback(null, true);
if (err)
return callback(err);
return callback(null, !stats.isDirectory());
});
};
const currentPath = __dirname; // B
let path = `${currentPath}/js200`;
checkDir(path, (err, isTrue) => { // C
if (err)
return console.log(err);
if (!isTrue) {
console.log('이미 동일한 디렉토리가 있습니다. 디렉토리명을 변경합니다.');
path = `${currentPath}/js200-new`;
}
fs.mkdir(path, (err) => {
if (err)
console.log(err);
console.log(`${path} 경로로 디렉토리를 생성했습니다.`);
});
});
위에 주석친 부분을 문단단위로 정리한다.
[A] "use strict"로 엄격한 모드로 실행하고
내장 모듈인 fs(파일 시스템)모듈을 가져온다.
파일생성 가능한 경로인지 해당경로를 체크하는 checkDir 콜백함수를 정의한다.
checkDir에서 fs의 stat()메소드를 통해 해당 path의 파일 존재 여부를 확인한다.
만약 에러가 날 경우 어떤 파일도 존재하지 않는 경우 파일을 생성할 수 있기 때문에
이 경우는 true를 전달하고, 기타 에러 발생시 에러정보만 전달한다.
에러가 아닌 경우 isDirectory()메소드를 통해 논리부정 연산자를 붙여 전달해준다.
isDirectory()메소드는 디렉토리에 이미 동일한 폴더가 있는지 알려준다.
isDirectory()메소드에 논리 부정 연산자를 붙여주는 이유는
isDirectory()가 true면 정상적으로 폴더를 생성할 수 없기 때문에 false를 전달하고
false면 비어있다는 뜻이므로 정상적으로 파일을 생성할 수 있기 때문에 true를 전달하기 때문이다.
[B] __dirname으로 현재 경로를 가져와서 특정 파일명을 붙여준다.
[C] 위에서 정의한 checkDir() 콜백함수를 실행한다.
에러가 발생되면 에러정보를 반환한다.
checkDir은 결국 정상적으로 폴더를 생성할 수 있으면
true를 반환하고 그렇지 않으면 false를 반환한다.
false를 반환했다면 디렉토리를 변경해서 폴더를 생성한다.
true를 반환했다면 해당경로에 정상적으로 폴더를 생성한다.
fs.stat 모듈을 사용 폴더를 생성했다.
하지만 fs.open(), fs.readFile(), fs.writeFile()와 같이
파일을 직접 접근하여 읽고 쓸 때는 반드시 접근 권한을 확인해야 하므로
fs.stat보단 fs.access 모듈이 권장된다.
fs.access 모듈 활용 방법은 다음에 포스팅 예정이다
fs.stat 모듈과 클래스에 대해 더 자세한 내용은 다음링크에서 확인할 수 있다
https://nodejs.org/docs/latest/api/fs.html#fs_fs_stat_path_callback
Node.js에서 다음과 같은 예외처리 방법 2가지를 예제를 통해 정리한다.
1. 비동기(Async) 모듈, 콜백함수
2. try-catch, throw
참고로
동기(Sync) 패턴에선 try-catch를 사용할 수 있지만
비동기(Async) 패턴에선 try-catch를 사용할 수 없다.
example.js
"use strict";
const cbFunc = (err, result) => {
if (err && err instanceof Error)
return console.error(err.message);
if (err)
return console.error(err);
console.log('에러가 발생하지 않음');
};
const asyncFunction = (isTrue, callback) => {
if (isTrue)
return callback(null, isTrue);
else
return callback(new Error('This is error!!'));
};
asyncFunction(true, cbFunc);
asyncFunction(false, cbFunc);
asyncFunction의 첫번째 인자에 어떤 연산의 결과값이 들어간다고 가정하고,
두번째 인자에 예외를 처리하는 비동기 콜백함수인 cbFunc를 넣어준다고 하자
연산의 결과값이 true(이상이 없는)인 경우 예외가 발생할 필요가 없으므로,
cbFunc의 예외변수에는 null을 전달하고 결과값은 true를 전달한다.
연산의 결과값이 false(이상이 있는)인 경우 예외를 발생시켜야 하므로,
cbFunc의 예외변수에 에러객체를 전달한다.
이 때, cbFunc는 에러가 빈 값이면 "에러가 발생하지 않음"을 출력하고,
에러정보가 있다면 에러메시지를 출력하는데
에러객체로 넘어온경우 객체에 담긴 메시지를 출력하고 그렇지 않은경우 그 메시지 자체를 출력한다.
실행결과
에러가 발생하지 않음
This is error!!
example.js
"use strict";
const fs = require('fs');
try {
const fileList = fs.readdirSync('/undefined/');
console.log('hello');
fileList.forEach(f => console.log(f));
} catch (err) {
if (err) console.error(err);
}
실행결과
{ Error: ENOENT: no such file or directory, scandir 'c:\undefined'
at Object.fs.readdirSync (fs.js:928:18)
at Object.<anonymous> (c:\Program Files\Git\usr\practicejs\JSExerciseFiles\part4\166.js:27:25)
at Module._compile (module.js:649:30)
at Object.Module._extensions..js (module.js:660:10)
at Module.load (module.js:561:32)
at tryModuleLoad (module.js:501:12)
at Function.Module._load (module.js:493:3)
at Function.Module.runMain (module.js:690:10)
at startup (bootstrap_node.js:194:16)
at bootstrap_node.js:666:3
errno: -4058,
code: 'ENOENT',
syscall: 'scandir',
path: 'c:\\undefined' }
내장 모듈인 fs(파일시스템) 모듈을 가져와서
특정경로에 있는 파일을 출력하는 상황이다.
"/undefined/" 경로의 파일리스트를 가져오는 순간(아무것도 없어서)
예외가 발생하여 바로 catch문으로 가게 된다.
따라서 try문 안에 있는 "hello"는 출력되지 않는다.
js에서 별도의 스레드에서 실행할 수 있는 웹 워커(Web Worker)를 예제를 통해 정리한다.
example.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>웹워커 예제</title>
</head>
<body>
<div>
<input type="number" id="number">
<button id="start-btn">피보나치수열 계산시작</button>
</div>
<div id="result"></div>
<script>
const result = document.getElementById('result');
let isCalculation = false;
if (window.Worker) {
const fibonacciWorker = new Worker('fibonacci.js'); // A
document.getElementById('start-btn').addEventListener('click', e => {
if (isCalculation) {
return;
}
const value = document.getElementById('number').value;
fibonacciWorker.postMessage({ num: value });
result.innerHTML = '계산중...';
isCalculation = true;
});
fibonacciWorker.onmessage = function(e) { // B
result.innerHTML= e.data;
isCalculation = false;
};
fibonacciWorker.onerror = function(error) { // C
console.error('에러 발생', error.message);
result.innerHTML= error.message;
isCalculation = false;
};
}
</script>
</body>
</html>
fibonacci.js
function fibonacci(num) {
if (num <= 1) {
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
}
onmessage = function(e) { // D
const num = e.data.num;
console.log('메인 스크립트에서 전달 받은 메시지', e.data);
if (num == null || num === "") {
throw new Error('숫자를 전달하지 않았습니다.');
}
const result = fibonacci(num);
postMessage(result);
}
전체적인 흐름을 설명하고 위에 주석친 부분을 문단단위로 정리한다.
계산시작 버튼을 누르면 입력한 숫자값을 fibonacci.js의 onmessage 콜백함수에 전달한다.
fibonacci.js는 전달받은 숫자값으로 계산을 수행한 뒤, 다시 example.html의 onmessage로
postMessage() 메소드를 통해 결과값을 전달한다. 단, 전달받은 숫자가 null이거나 공백일 경우
에러를 반환하여 example.html에 onerror 콜백함수가 호출된다.
[A] 계산시작 버튼을 누르면 입력한 숫자값을 fibonacci.js의 onmessage 콜백함수에 전달한다.
[B] fibonacci.js에서 postMessage()로 데이터 전달 시 호출되는 콜백함수를 정의한다. 전달받은 결과값은 화면에 표시한다.
[C] fibonacci.js에서 에러반환시 호출되는 콜백함수를 정의한다.
[D] example.html에서 postMessage()로 데이터 전달 시 호출되는 콜백함수를 정의한다. 전달받은 데이터가 null이거나 공백이면 에러를 반환하고 그렇지 않으면 계산을 수행해서 결과값을 반환한다.
js에서 iframe에서 main-frame으로 메시지를 전달하는 방법을 예제를 통해 정리한다.
example.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>iframe 메시지 교환하기</title>
</head>
<body>
<div>
<label>결제금액 :</label> <b>20000원</b>
<br />
<button id="checkout-btn">카드입력</button>
</div>
<iframe
id="card-payment"
width="500px"
height="200px"
frameborder="0"
></iframe>
<script>
const iWindow = document.getElementById("card-payment").contentWindow;
document.getElementById("checkout-btn").addEventListener("click", e => { // A
iWindow.location = "payment.html";
});
window.addEventListener("message", e => { // B
if(!e.data.hasOwnProperty('cardNumber'))
return;
console.log(e);
});
</script>
</body>
</html>
payment.html
<script>
function submitForm() { // C
const form = document.getElementById("card-form");
const formData = new FormData(form);
const formObj = {
cardNumber: formData.get("cardNumber"),
holderName: formData.get("holderName")
};
window.parent.postMessage(formObj, "*");
}
</script>
<form id="card-form" onsubmit="submitForm()">
<div>
<label>카드번호</label>
<input type="text" name="cardNumber" />
</div>
<div>
<label>이름</label>
<input type="text" name="holderName" />
</div>
<button type="submit">결제하기</button>
</form>
전체적인 흐름을 설명하고 위에 주석친 부분을 문단단위로 정리한다.
main-frame인 example.html에 iframe이 내장되 있고 "카드입력" 버튼을 클릭하면
iframe의 내부페이지가 payment.html로 변경된다. 그 후, 카드번호와 이름을 입력해서
결제하기로 submit하면 해당 폼 데이터를 payment.html에서 객체로 생성해서
postMessage로 main-frame에 전달한다.
그런데 payment.html에서 submit버튼을 눌러야
158.html의 message 이벤트리스너가 호출되는 것이 정상인데
Live Server로 실행해보면 아무것도 건들지 않았는데도 뭔가 예약된 메시지가 있는 것인지
message 이벤트 리스너가 기본적으로 2번 호출되고 시작한다. 전달받은 데이터는
콘솔내용을 확인해보니 data의 id가 "js"인 것과 "patterns"이다.
이게 왜 오는 것인지 확인중이며, 이 때 카드번호와 이름이 넘어오진 않기 때문에
iframe과는 연관이 없어보인다. 그래서 일단 카드번호를 전달하는 프로퍼티인
"cardNumber"가 존재하는지 hasOwnProperty()메소드로 확인하여 처리하였다.
[A] 카드입력 버튼을 누르면 내부페이지를 payment.html로 location을 통해 변경한다.
[B] iframe에서 postMessage로 전달하면 message 이벤트가 발생한다. 전달받은 메시지도 event 파라미터로 접근가능하다.
[C] submit 버튼이 눌렸을 때 postMessage()를 통해 main-frame으로 데이터를 전달한다.
postMessage는 안전하게 cross-origin통신을 할 수 있게 한다.
페이지와 생성된 팝업 간의 통신이나, 페이지와 페이지 안의 iframe 간의 통신할 때 사용할 수 있다.
함수원형은 다음과 같다.
postMessage(message, targetOrigin, [transfer])
쉽게 말하면 postMessage(데이터, 타겟)이다.
타겟은 주의할 점이 있는데 비밀번호가 전송된다면, 악의적인 제 3자가 가로챌 수 있기 때문에 데이터가
유출될 수 있다. 따라서 다른 window의 document의 위치를 알고 있다면 "*"와 같이
별도로 지정하지 않는 것 보다 특정한 대상을 지정해주는 것이 좋다.
참고 https://developer.mozilla.org/ko/docs/Web/API/Window/postMessage
js에서 iframe을 제어하는 방법을 예제를 통해 정리한다.
example.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>iframe 예제</title>
</head>
<body>
<h1>main-frame (바깥 문서)</h1>
<iframe id="iframe1" src="./1.html" frameborder="0"
width="100%" height="500px"></iframe>
<script>
const iframe1 = document.getElementById('iframe1'); // A
iframe1.addEventListener('load', e => {
const iframeDocument = iframe1.contentDocument;
iframeDocument.body.style.backgroundColor = "blue";
const newEl = document.createElement('div'); // B
newEl.innerHTML = '<h1>iframe (안쪽 문서)</h1>';
newEl.style.color = 'white';
iframeDocument.body.appendChild(newEl);
setTimeout(() => { // C
const iframeWindow = iframe1.contentWindow;
iframeWindow.location = 'https://google-analytics.com';
}, 3000);
});
</script>
</body>
</html>
1.html
<!DOCTYPE html>
<html>
<head>
<title>iframe</title>
</head>
<body>
</body>
</html>
html에서 iframe태그에 src속성에 내장할 페이지를 지정할 수 있으며,
위에 주석친 부분을 문단단위로 정리한다.
[A] contentDocument를 통해 iframe의 독립적인 document를 접근해서 body의 배경색을 지정할 수 있다.
[B] iframe에 div요소를 추가할 때는 document.createElment로 div를 생성해서
iframe의 독립적인 document인 contentDocument.body에 appendChild하면 된다.
[C] contentWindow를 통해 iframe의 독립적인 window를 접근해서 location을 변경할 수 있다.
다만, 동일 출처 정책이라 해서 서버에서 응답하는 HTTP 헤더는 X-Frame-Options가 'sameorigin'으로 설정되어 있기 때문에
iframe통해 다른페이지를 내장할 순 있지만 동일 출처 정책에 부합하지 않으면 iframe의 window객체나 document객체에 접근하여 element를 수정하는 행위는 할 수 없다. 즉, iframe에 다른 페이지를 연결해서 element를 수정하는 행위는
프로토콜(http/https), 포트(ex:8080), 도메인(com/co.kr/net/org)이 모두 같을 때 가능하다.
로컬이미지 파일을 드래그 앤 드랍하면
브라우저에 이미지가 표시되는 예제의 내용을 정리한다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로컬 파일을 브라우저에서 읽기 예제</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="file-box" class="dot-box">
이미지 파일을 선택한 후 이곳에 끌어서 놓아주세요.
</div>
<output id="result"></output>
<script>
var dropZone = document.getElementById('file-box');
dropZone.addEventListener('dragover', e => {
e.stopPropagation(); // 상위 전파 막기
e.preventDefault(); // 기본 동작 막기. 브라우저가 이미지화면으로 전환하는 것을 막기 위함
});
dropZone.addEventListener('drop', e => {
e.stopPropagation();
e.preventDefault();
const files = e.dataTransfer.files;
Array.from(files) // files를 배열화
.filter(file => file.type.match('image.*')) // file type이 image인것만 필터링
.forEach(file => { // 여러개의 이미지 처리하기 위해
const reader = new FileReader();
reader.onload = (e) => {
const imgEl = document.createElement('img');
imgEl.src = e.target.result;
imgEl.title = file.name;
document.getElementById('result').appendChild(imgEl);
};
reader.readAsDataURL(file);
});
});
</script>
</body>
</html>
소스코드를 간략히 설명하자면 "dragover"와 "drag"시 이벤트가 전파되는 것을 방지한다.
브라우저에 이미지를 끌어다 놓으면 이미지화면으로 전환되기 때문에 기본 동작을 방지한다.
드랍 시킨 file들을 Array.from()으로 배열화 시킨다.
file중 image파일인 것만 필터링한다.
파일을 하나씩 처리하는데 FileReader()를 통해 onload 이벤트리스너를 콜백함수(비동기)로 등록한다.
(onload는 리소스를 다 읽었을 때 이벤트가 발생된다)
파일을 다 읽을 때마다 'img'라는 엘리먼트를 생성해서 'result'란에 이미지를 표시한다.
다음과 같이 로컬이미지파일을 드래그 앤 드랍하여 업로드하면 화면에 표시된다.