StoryCode

게시판.이미지.Image.Upload

JavaScript, ECMAScript
반응형

# 참조 : https://dahanweb.tistory.com/58

# 서버 사이드 코드는 없음.

test.imageuploadboard / index.html

<html>
<head>
<script type="text/javascript" src="/jquery.plugin/1.11.2/jquery-1.11.2.min.js"></script>
<script>
function readInputFile(e)
{
    var sel_files = [];

    sel_files = [];
    $("#imagePreview").empty();

    var files = e.target.files;
    var fileArr = Array.prototype.slice.call(files);
    var index = 0;

    fileArr.forEach(function(f)
    {
        if (!f.type.match("image/.*"))
        {
            alert("이미지 확장자만 업로드 가능합니다.");
            return;
        };

        if(files.length < 11)
        {
            sel_files.push(f);
            var reader = new FileReader();
            reader.onload = function(e)
            {
                var html = `<a id=img_id_${index}><img src=${e.target.result} data-file=${f.name} /></a>`;
                $('#imagePreview').append(html);
                index++;
            };

            reader.readAsDataURL(f);
        }
    })

    if(files.length > 11){
        alert("최대 10장까지 업로드 할 수 있습니다.");
    }
}

</script>
</head>
<body>
<form>
  <input type="file" id="real-input" class="image_inputType_file" accept="img/*" required multiple>
</form>
  <button class="browse-btn">사진업로드</button>

  <h3>이미지 미리보기</h3>
  <div id="imagePreview">
    <img id="img" />
  </div>
<script>
$("#real-input").on("change",readInputFile);

const browseBtn = document.querySelector(".browse-btn");
const realInput = document.querySelector("#real-input");

browseBtn.addEventListener("click",()=>{ realInput.click(); });
</script>
</body>
</html>
반응형

'JavaScript, ECMAScript' 카테고리의 다른 글

Chrome.Console.elapsed.time.시간측정  (0) 2022.12.03
음성,Speak,Speech  (0) 2022.12.01
Chrome Browser Rendering FPS 측정  (0) 2022.11.20
Indexed DB  (0) 2022.08.03
디버깅.Debugging.VSC.VisualStudioCode.연동  (0) 2022.07.28

Chrome.Console.elapsed.time.시간측정

JavaScript, ECMAScript
반응형

console.time('my time');

document.querySelector('a');

console.timeEnd('my time');

반응형

'JavaScript, ECMAScript' 카테고리의 다른 글

게시판.이미지.Image.Upload  (0) 2023.04.28
음성,Speak,Speech  (0) 2022.12.01
Chrome Browser Rendering FPS 측정  (0) 2022.11.20
Indexed DB  (0) 2022.08.03
디버깅.Debugging.VSC.VisualStudioCode.연동  (0) 2022.07.28

음성,Speak,Speech

JavaScript, ECMAScript
반응형

u = new SpeechSynthesisUtterance("우리는 말안하고 살수가 없나~");

window.speechSynthesis.speak(u);

반응형

'JavaScript, ECMAScript' 카테고리의 다른 글

게시판.이미지.Image.Upload  (0) 2023.04.28
Chrome.Console.elapsed.time.시간측정  (0) 2022.12.03
Chrome Browser Rendering FPS 측정  (0) 2022.11.20
Indexed DB  (0) 2022.08.03
디버깅.Debugging.VSC.VisualStudioCode.연동  (0) 2022.07.28

Chrome Browser Rendering FPS 측정

JavaScript, ECMAScript
반응형

1) 우측 상단 점세개 클릭

2) Run command 선택 ( C + S + P )

3) show frames per second 입력후 엔터

4) 좌측 상단에 프레임이 보임.

반응형

Indexed DB

JavaScript, ECMAScript
반응형

# 참조 : https://www.youtube.com/watch?v=mHJDtDM_wHc 

# 참조 : https://seomal.com/map/1/231

 

1) 용량

Cookie는 수KB 만 저장 가능

Local Storage 는 수MB 저장 가능

IndexedDB (Web DB) 는 Chrome 은 디스크 80%, Firefox 는 50%, Safari 는 1GB 저장 가능

 

2) 서버 전송 여부

Cookie 는 자동 포함되서 전송되고, 나머지는 자동 전송안되고 프론트에서 수작업으로 보내야 함.

 

3) 저장 가능 타입

Cookie / LocalStorage 문자열만 저장되나, Indexed DB 는 DB 이므로 다양한 포맷 저장 가능

 

4) 동작주의

웹서버 필요. (Visual Studio Code 에서는 Live Server 설치후, html 오른쪽 클릭 메뉴에서 "Open with Live Server" 선택

ffile:/// 로는 보안때문인지 실행이 안됨 .

 

5) 구조

DB Indexed DB
Database Database
Table(Entity) Object Store
Row(Attribue, Tuple) Object

 

6) 명령

     
#1.Database 신규/기존 오픈 요청 const databaseopenrequest = indexedDB.open('myservicedatabase', 1)
* 1 은 개발자(회사)가 정하는 임의의 버전이다.
이유인즉슨, DB 자체는 클라이언트의 브라우저에 있다. 그러다보니 서버에서 클라이언트의 DB 내용을 변경하고자 할 경우, indexedDB.open 에서 이 버전을 2 로 바꾼 뒤 사용자가 브라우저로 접속하면, 아래 "#3.Database 버전 업그레이드" Event 가 발생한다.
#2.Database 신규/기존 오픈 성공 후, db instance 돌려받기 databaseopenrequest.addEventListener('success',
    function(event)
    {
        databaseinstance = event.target.result;
    }
);

databaseopenrequest.addEventListener('error',
    function(event)
    {
    }
);


* 여기서 databaseinstance 가 실제 database instance 이다.
#3.Database 버전 업그레이드 databaseopenrequest.addEventListener('upgradeneeded',
    function(event)
    {
        databaseinstance = event.target.result;
        if (event.oldVersion < 1) // *2
        {
            databaseinstance.createObjectStore('boardmaster_tbl', {keyPath:'boardmasterid', autoIncrement:true});
        }

        if (event.oldVersion < 2) // *2
        {
            databaseinstance.createObjectStore('boarddetail_tbl', {keyPath:'boarddetailid', autoIncrement:true});
        }
    }
);

*1 event.oldVersion == 0 이면 최초 생성.

*2 고민사항) 버전 1부터 n까지 차근 차근 올라간 사람은 괜찮은데, 신규 회원이 갑자기 버전 10부터 접근시 처리도 고민해야 할 듯.
#4. Insert
- prompt 로 입력받은 값 넣기
let boardmaster_tbl = databaseinstance.transaction('boardmaster_tbl', 'readwrite').objectStore('boardmaster_tbl);

let insertrequest = boardmaster_tbl.add({title:promot('enter title'), body:prompt('enter body'));

insertrequest.addEventListener('success', function(event) {event.target.result}); // 성공시 처리
#5. Select let boardmaster_tbl = databaseinstance.transaction('boardmaster_tbl', 'readonly').objectStore('boardmaster_tbl);

let selectrequest = boardmaster_tbl.get(prompt('enter id'));

selectrequest.addEventListener('success', function(event) {event.target.result}); // 성공시 처리
#6. Select All let boardmaster_tbl = databaseinstance.transaction('boardmaster_tbl', 'readonly').objectStore('boardmaster_tbl);

let selectrequest = boardmaster_tbl.getAll();

selectrequest.addEventListener('success', function(event) {event.target.result}); // 성공시 처리

* 주의 : 데이타 많아지면  IDBCursor 사용 필요 ( 별도로 알아볼것 )
#7. Update let boardmaster_tbl = databaseinstance.transaction('boardmaster_tbl', 'readwrite').objectStore('boardmaster_tbl);

let updaterequest = boardmaster_tbl.put({id:Number(prompt('enter id')),title:promot('enter title'), body:prompt('enter body'));

updaterequest.addEventListener('success', function(event) {event.target.result}); // 성공시 처리
#8. Delete let boardmaster_tbl = databaseinstance.transaction('boardmaster_tbl', 'readwrite').objectStore('boardmaster_tbl);

let updaterequest = boardmaster_tbl.delete(Number(prompt('enter id')));

updaterequest.addEventListener('success', function(event) {event.target.result}); // 성공시 처리

 

 

 

 

반응형

디버깅.Debugging.VSC.VisualStudioCode.연동

JavaScript, ECMAScript
반응형

# 참고 : https://subicura.com/2018/02/14/javascript-debugging.html

 

 

     
1. 이벤트 Break Point 1) Debug Mode . Sources . 우측 Event Listener Breakpoints 

2-1) 마우스 클릭 이벤트 > Mouse . Click 을 클릭
2-2) Submit 버튼 이벤트 > Submit 을 클릭
2. JQuery Break Point 1) Debug Mode . Sources . 우측 Event Listener Breakpoints 
2) Source 코드창 바로 아래 { } 을 클릭하면, minified 된 코드가 beautifer 된다.
3) JQuery Code 를 디버깅 할게 아니라서, JQuery Code 는 제외하자 .
Settings . BlackBoxing . Add Pattern . " /.*jquery.*\.js$ " 따옴표 안 내용 추가
4) 추가하면 JQuery 에는 더이상 BreakPoint 가 잡히지 않는다 .
3. Fetch Break Point Ex> github 에 네트워크 요청이 있을때
1) 
Debug Mode . Sources . 우측 XHR/fetch Breakpoints
2) + 를 누른다 .
3) URL contains 'github' 를 기록
4. Dom Change Break Point 1) Elements 메뉴에서 소스상 원하는 위치에서 오른쪽 클릭
2) Break On.subtree modifications
3)
5. React Example static/bundle.js 이 직접 디버깅이 안됨.
bundle.js 파일 최하단에 적힌 //# sourceMappingURL=bundle.js.map 파일을 열어보면,
sources 파일 목록확인이 가능하다 . ( 하지만 복잡해서 힘들 듯 )

https://web.dev/learn/

 

반응형

'JavaScript, ECMAScript' 카테고리의 다른 글

Chrome Browser Rendering FPS 측정  (0) 2022.11.20
Indexed DB  (0) 2022.08.03
Swallow Copy, Deep Copy, json 복사시 문제점과 대응  (0) 2022.07.25
Callback, Promise, Async/Await  (0) 2022.04.04
import, export  (0) 2021.09.28

Swallow Copy, Deep Copy, json 복사시 문제점과 대응

JavaScript, ECMAScript
반응형

# 참조 : https://paperblock.tistory.com/218

 

● Javascript 가 Json / 배열 객체를 다루는 법 .

요약하면 포인터 방식이다 .

1: const original = {name: '철수', age: 12};
2: const clone = original;
3: console.log(clone === original) //true

4: clone.name = '영희';
5: console.log(original.name) //영희

1열 : javascript 는 {} 객체를 할당하고 그 주소를 original 에 담는다.

2열 : clone 에 original 에 담긴 주소를 담는다 . 즉, orignal 과 clone 은 같은 객체를 가르킨다 . call by ref .

 

● Javascript 가 Json / 배열 객체를 다룰 때 특이사항

javascript 에는, 객체를 복사할 때 Object.assign() 이나 Paste객체= {...Copy객체 } ( ES6 Spread Operator )방식을 사용한다.

1: const original = {name: '철수', age: 12};
2: const clone = Object.assign({}, original);
3: clone.name = '영희'

4: console.log(original) // {name: '철수', age: 12}
5 :console.log(clone); // {name: '영희', age: 12}
1: const original = {profile: {name:'철수', age:12}, grade: 'A'};
2: const clone = { ...original };

3: clone.profile.name = '영희';
4: clone.grade = 'B';

5: console.log(original) // {profile: {name: '영희', age: 12}, grade: 'A'}
6: console.log(clone); // {profile: {name: '영희', age: 12}, grade: 'B'}

 

문제는,

1: const original = {profile: {name:'철수', age:12}, grade: 'A'};
2: const clone = { ...original };

3: clone.profile.name = '영희';
4: clone.grade = 'B';

5: console.log(original) // {profile: {name: '영희', age: 12}, grade: 'A'}
6: console.log(clone); // {profile: {name: '영희', age: 12}, grade: 'B'}

1열에서 Json 객체안에 Json 객체가 있을 경우 복사가 특이하다. ( 이를 Swallow Copy = 얕은 복사라 한다 )

즉, original 에는 바깥쪽 Json 객체 주소를 가지는데, 문제는 안쪽 Json 객체인 {name:'철수', age:12} 도 별도의 Json 객체로 주소를 가진다 . 즉 profile 은 다시 Json 객체 주소이다.

 

그렇기 때문에 3열에서 name = '영희' 로 바꾸면 original 과 clone 의 name 이 모두 바뀐다.

 

그래서 객체안의 객체까지 "복사" 하는게 Deep Copy 이다.

 

 Deep Copy

과거에는 Deep Copy 를

const clone = JSON.parse(JSON.stringify(original));

와 같이 parse 와 stringfy 직렬화를 사용하였다 .

하지만, 엔진에서 parse 속도 개선하였으나 그래도 느렸고, 심지어 복잡한 구조의 경우 복사에 문제가 발생했다 .

* 복잡한 구조 = Recursive data structures, Built-in types (Date, Map, Set, Date, RegExp, ArrayBuffer...), Functions

 

이를 Application 레벨에서 해결하려고 나온게 underscore와 Lodash.deepclone 함수 이다.

하지만 이젠 엔진레벨에서 모든 브라우저저가 StructuredClone() 을 지원한다.

 

StructuredClone

사용법은 아래와 같다.

const pasteobject = structuredClone(copyobject)

* 문법 ) structuredClone(value, { transfer })
반응형

'JavaScript, ECMAScript' 카테고리의 다른 글

Indexed DB  (0) 2022.08.03
디버깅.Debugging.VSC.VisualStudioCode.연동  (0) 2022.07.28
Callback, Promise, Async/Await  (0) 2022.04.04
import, export  (0) 2021.09.28
CORS.ajax.fetch.어떻게 해도 CORS 가 발생할 경우  (0) 2021.08.31

Callback, Promise, Async/Await

JavaScript, ECMAScript
반응형

# 참고 : https://www.daleseo.com/js-async-callback/#%EC%BD%9C%EB%B0%B1-%ED%95%A8%EC%88%98%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC

# 참고 : https://www.daleseo.com/js-async-promise/

# 참고 : https://www.daleseo.com/js-async-async-await/

 

[자바스크립트] 비동기 처리 1부 - Callback

자바스크립트의 콜백 함수와 비동기 함수애 대해서 혼란스러워 하시는 분들이 주변에 많은 것 같아서 개념 정리를 해보고자 합니다. 이번 포스팅에서는 실제 프로젝트에서 자주 접할 수 있는 유저 데이터 조회 시나리오를 통해 콜백 함수를 이용한 비동기 처리에 대해서 알아보겠습니다.

콜백 함수

유저 ID를 인자로 받아 DB나 API 연동 없이 임의의 유저 객체를 리턴하는 findUser()라는 함수를 작성해보겠습니다.

function findUser(id) {
  const user = {
    id: id,
    name: "User" + id,
    email: id + "@test.com",
  };
  return user;
}

const user = findUser(1);
console.log("user:", user);
  • 결과
user: {id: 1, name: "User1", email: "1@test.com"}

위와 같이 우리가 흔히 생각하는 일반적인 함수란 입력(파라미터)이 있고 출력(리턴값)이 있습니다.

하지만 자바스크립트에서는 출력값이 없고 그 대신에 콜백 함수를 입력받는 함수들이 많이 있습니다. 콜백 함수는 다른 함수에 인자로 넘어가서 실행될 로직을 담게 됩니다.

예를 들면, 위 코드는 아래와 같이 다른 스타일로 동일한 결과를 출력하도록 재작성할 수 있습니다.

function findUserAndCallBack(id, cb) {
  const user = {
    id: id,
    name: "User" + id,
    email: id + "@test.com",
  };
  cb(user);
}

findUserAndCallBack(1, function (user) {
  console.log("user:", user);
});
  • 결과
user: {id: 1, name: "User1", email: "1@test.com"}

10번째 줄의 findUserAndCallBack() 함수의 호출부를 보면 두번째 인자로 콜백 함수를 선언하여 넘겼습니다. 따라서 7번째 줄의 findUserAndCallBack() 함수가 실행될 때 cb 매개 변수는 콜백 함수는 할당 받으며, cb(user); 가 실행될 때, 이 콜백 함수가 실행되게 됩니다.

위 두 코드의 차이점은 findUser() 함수는 결과값을 리턴하고 함수 외부에서 결과값을 이용하여 작업을 수행하는 반면에, findUserAndCallBack() 함수는 결과값을 이용해 해야할 작업까지 함수 내부에서 수행해주기 때문에 결과값을 굳이 리턴할 필요가 없습니다.

자바스크립트에서는 함수도 숫자나 문자처럼 변수에 할당할 수 있는 하나의 값이기 때문에 콜백 함수를 다른 함수의 인자로 넘기는 것은 매우 자연스러운 현상입니다. 그래서 위 두 코드는 단순히 스타일 차이로도 볼 수 있지만 자바스크립트 특유의 비동기 처리가 들어가게 되면 얘기가 약간 달라지게 됩니다.

콜백 함수를 통한 비동기 처리

비동기(Asynchronous) 함수란 쉽게 설명하면 호출부에서 실행 결과를 가다리지 않아도 되는 함수입니다. 반대로 동기 함수(Synchronous) 함수는 호출부에서 실행 결과가 리턴될 때 까지 기다려야 하는 함수입니다.

비동기 함수의 이러한 Non-blocking 이점 때문에, 자바스크립트처럼 싱글 쓰레드 환경에서 실행되는 언어에서 광범위하게 사용됩니다. 예를 들어, 브라우져에서 어떤 로직이 동기 함수만으로 실행될 경우, 기다리는 시간이 많아져서 사용자 경험에 부정적인 영향을 미칠 것입니다. 또한, 비동기 함수를 사용하면 로직을 순차적으로 처리할 필요가 없기 때문에 동시 처리에서도 동기 함수 대비 유리한 것으로 알려져있습니다.

하지만 코딩을 하는 개발자 입장에서는 비동기 함수는 동기 함수에 비해서 좀 덜 직관적으로 느껴질 수 있습니다. 동기 함수처럼 순차적 처리가 보장되지 않기 때문에 아래에 위치한 코드가 위에 위치한 코드보다 먼저 실행될 수 있기 때문입니다.

자바 스크립트에는 setTimeout() 이라는 대표적인 내장 비동기 함수가 있습니다. setTimeout()은 두 개의 매개 변수를 받는데, 첫번째는 실행할 작업 내용을 담은 콜백 함수이고, 두번째는 이 콜백 함수를 수행하기 전에 기다리는 밀리초 단위 시간입니다. 즉, setTimeout() 함수는 두번째 인자로 들어온 시간 만큼 기다린 후에 첫번째 인자로 들어온 콜백 함수를 실행해줍니다.

setTimeout() 함수에 대한 좀 더 자세한 내용은 관련 포스팅을 참고바랍니다.

실제 프로젝트에서 DB나 API를 통해서 유저 데이터를 얻어오는 경우, 필연적으로 이러한 latency가 발생하게 됩니다. 이러한 상황을 시뮬레이션 하기 위해서 setTimeout()을 이용하여 위 섹션에서 작성했던 findUser() 함수를 수정하였습니다. const가 아닌 let을 이용해서 user 로컬 변수를 선언하고 setTimeout() 함수를 통해 0.1초 후에 user 변수에 객체를 할당하였습니다.

function findUser(id) {
  let user;
  setTimeout(function () {
    console.log("waited 0.1 sec.");
    user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
  }, 100);
  return user;
}

const user = findUser(1);
console.log("user:", user);
  • 결과
user: undefined
waited 0.1 sec.

코드를 실행해보면 예상치못한 순서로 코드가 실행됨을 알 수 있습니다. 3번째 줄의 setTimeout()은 비동기 함수의 호출이기 때문에 실행이 완료될 때 까지 기다리지 않고 다음 라인인 11번째 줄로 넘어가버립니다. 따라서 14번째 줄의 findUser(1)는 undefined를 리턴하게 되고 user 변수에는 그대로 undefined가 할당됩니다. 0.1초 후에 setTimeout() 함수의 첫번째 인자로 넘어간 콜백 함수가 실행되면서 waited 0.1 sec.가 출력되고 user 로컬 변수에 원하는 객체가 할당되었지만 이미 때 늦은 상황이 되었습니다.

이와 같이 setTimeout()과 같은 비동기 함수를 호출하게 되면, 함수의 실행이 완료도 되기 전에 다음 라인이 실행되어 버리기 때문에 각별히 주의를 해야합니다.

 

이와 같이 코드 실행 순서가 뒤죽박죽이 될 수 있는 난처한 상황에서는 이전 섹션에서 했던 것 처럼 콜백 함수를 이용해서 해결할 수 있습니다. 함수로 부터 결과값을 리턴 받기를 포기하고, 결과값을 이용해서 처리할 로직을 콜백 함수에 담아 인자로 던지면 됩니다.

function findUserAndCallBack(id, cb) {
  setTimeout(function () {
    console.log("waited 0.1 sec.");
    const user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
    cb(user);
  }, 100);
}

findUserAndCallBack(1, function (user) {
  console.log("user:", user);
});
  • 결과
waited 0.1 sec.
user: {id: 1, name: "User1", email: "1@test.com"}

이번에는 findUserAndCallBack() 함수의 2번째 인자로 결과값을 이용해서 실행될 로직을 넘겼고, setTimeout() 함수는 0.1초 후에 이 콜백 함수를 호출하였습니다. 이와 같이 비동기 함수를 호출할 때는 결과값을 리턴 받으려고 하지말고, 결과값을 통해 처리할 로직을 콜백 함수로 넘기는 스타일로 코딩을 해줘야 예상된 결과르 얻을 수 있습니다.

 

하지만 자바스크립트 프로젝트가 점점 더 복잡해지면서 최근에는 콜백 함수를 인자로 넘겨서 비동기 처리를 하는 스타일을 피하는 추세입니다. 왜냐하면 콜백 함수를 중첩해서 사용하게 되면 계속해서 코드를 들여쓰기 해야하고 그러다보면 코드 가독성이 현저하게 떨어지게 되기 때문입니다. 결국, 많은 개발자들이 콜백 지옥이라고 불리는 끔찍한 상황을 겪게 되었고 최근에는 Promise나 async/await를 이용하는 방법들로 대체되고 있습니다.

다음 포스팅에서는 Promise나 async/await를 이용해서 좀 더 코드 가독성을 해치지 않으면서 비동기처리하는 방법에 대해서 알아보겠습니다.

후속 포스팅

전체 코드

[자바스크립트] 비동기 처리 2부 - Promise

많은 분들이 자바스크립트 공부를 하시다가 프라미스(Promise)에 때문에 그만 두시곤 합니다. 이번 포스팅에서는 자바스크립트에서 비동기 처리를 위해 굉범위하게 사용되는 Promise에 대해서 알아보겠습니다.

콜백 함수를 통한 비동기 처리의 문제점

ES6에서 Promise가 도입되어 지금처럼 널리 사용되기 이전에는 주로 콜백 함수를 다른 함수의 인자로 넘겨서 비동기 처리를 코딩을 했었습니다. 예를 들어, 다음 코드를 보시면 findUserAndCallBack() 함수를 호출할 때, 두번째 인자로 콜백 함수가 넘어갑니다. 그리고 findUserAndCallBack() 함수 안에서 인자로 넘어온 콜백 함수가 호출되고 있습니다.

findUserAndCallBack(1, function (user) {
  console.log("user:", user);
});

function findUserAndCallBack(id, cb) {
  setTimeout(function () {
    console.log("waited 0.1 sec.");
    const user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
    cb(user);
  }, 100);
}
  • 결과
waited 0.1 sec.
user: {id: 1, name: "User1", email: "1@test.com"}

단순한 코드를 작성할 때는 위와 같이 전통적인 방식으로 콜백 함수를 통해 비동기 처리를 해도 큰 문제가 발생하지 않습니다. 하지만, 콜백 함수를 중첩해서 연쇄적으로 호출해야하는 복잡한 코드의 경우, 계속되는 들여쓰기 때문에 코드 가독성이 현저하게 떨어지게 됩니다. 자바스크립트 개발자들 사이에서 소위 콜백 지옥이라고 불리는 이 문제를 해결하기 위해 여러가지 방법들이 논의 되었고 그 중 하나가 Promise 입니다.

콜백 함수를 인자로 넘겨서 비동거 처리를 하는 자바스크립트 코딩 방법은 다음 포스팅를 참조 바랍니다.

Promise의 개념

Promise는 현재에는 당장 얻을 수는 없지만 가까운 미래에는 얻을 수 있는 어떤 데이터에 접근하기 위한 방법을 제공합니다. 당장 원하는 데이터를 얻을 수 없다는 것은 데이터를 얻는데까지 지연 시간(delay, latency)이 발생하는 경우를 말합니다. I/O나 Network를 통해서 데이터를 얻는 경우가 대표적인데, CPU에 의해서 실행되는 코드 입장에서는 엄청나게 긴 지연 시간으로 여겨지기 때문에 Non-blocking 코드를 지향하는 자바스크립트에서는 비동기 처리가 필수적입니다.

(참고. Java에 익숙하신 분들은 Future 클래스에 해당하는 녀석이라고 보시면 됩니다.)

예를 들면, 위에서 봤던 코드는 Promise를 이용해서 아래와 같이 재작성할 수 있습니다.

findUser(1).then(function (user) {
  console.log("user:", user);
});

function findUser(id) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log("waited 0.1 sec.");
      const user = {
        id: id,
        name: "User" + id,
        email: id + "@test.com",
      };
      resolve(user);
    }, 100);
  });
}
  • 결과
waited 0.1 sec.
user: {id: 1, name: "User1", email: "1@test.com"}

위 코드는 콜백 함수를 인자로 넘기는 대신에 Promise 객체를 생성하여 리턴하였고, 호출부에서는 리턴받은 Promise 객체에 then() 메서를 호출하여 결과값을 가지고 실행할 로직을 넘겨주고 있습니다. 콜백 함수를 통해 비동기 처리를 하던 기존 코드와 가장 큰 차이점은 함수를 호출하면 Promise 타입의 결과값이 리턴되고, 이 결과값을 가지고 다음에 수행할 작업을 진행한다는 것입니다. 따라서 기존 스타일보다 비동기 처리 코드임에도 불구하고 마치 동기 처리 코드 처럼 코드가 읽히기 때문에 좀 더 직관적으로 느껴지게 됩니다.

자 그럼, 지금부터 Promise를 사용하는 문법적인 사항에 대해서 알아보겠습니다.

Promise 생성 방법

먼저 Promise 객체를 리턴하는 함수를 작성하는 방법에 대해서 알아보겠습니다.

Promise는 객체는 new 키워드와 생성자를 통해서 생성할 수 있는데 이 생성자는 함수를 인자로 받습니다. 그리고 이 함수 인자는 reslove와 reject라는 2개의 함수형 파라미터를 가집니다.

따라서 아래와 같은 모습으로 Promise 객체를 생성해서 변수에 할당할 수 있습니다.

const promise = new Promise(function(resolve, reject) { ... } );

실제로는 변수에 할당하기 보다는 어떤 함수의 리턴값으로 바로 사용되는 경우가 많고, ES6의 화살표 함수 키워드를 더 많이 사용하는 것 같습니다.

function returnPromise() {
  return new Promise((resolve, reject) => { ... } );
}

생성자의 인자로 넘어가는 함수 인자의 바디에서는 resolve()나 reject() 함수를 정상 처리, 예외 발생 여부에 따라 적절히 호출해줘야 합니다. 일반적으로 resolve() 함수의 인자로는 미래 시점에 얻게될 결과를 넘겨주고, reject() 함수의 인자로는 미래 시점에 발생할 예외를 넘겨줍니다.

예를 들어, 나눗셈 함수를 Promise를 리턴하도록 구현해보겠습니다. (나눗셈을 비동기 처리할 이유는 없지만 이해하기 쉬운 간단한 예시를 위해서…)

function devide(numA, numB) {
  return new Promise((resolve, reject) => {
    if (numB === 0) reject(new Error("Unable to devide by 0."));
    else resolve(numA / numB);
  });
}

그리고 먼저 정상적인 인자를 넘겨 devide() 함수를 호출해서 Promise 객체를 얻은 후 결과값을 출력해보겠습니다.

devide(8, 2)
  .then((result) => console.log("성공:", result))
  .catch((error) => console.log("실패:", error));
  • 결과
성공: 4

이번에는 비정상적인 인자를 넘겨보겠습니다.

devide(8, 0)
  .then((result) => console.log("성공:", result))
  .catch((error) => console.log("실패:", error));
  • 결과
실패: Error: Unable to devide by 0.
    at Promise (<anonymous>:4:20)
    at new Promise (<anonymous>)
    at devide (<anonymous>:2:12)
    at <anonymous>:1:1

출력 결과를 통해 정상적인 인자를 넘긴 경우 then() 메서드가 호출되고, 비정상적인 인자를 넘긴 경우 catch() 메서드가 호출되었다는 것을 알 수 있습니다.

다음 섹션에서 Promise 객체로 부터 얻게 될 미래 시점의 결과값이나 예외값을 접근하기 위해 사용하는 then()과 catch() 메서드에 대해서 알아보겠습니다.

Promise 사용 방법

실제 코딩을 할 때는 위와 같이 Promise를 직접 생성해서 리턴해주는 코드 보다는 어떤 라이브러리의 함수를 호출해서 리턴 받은 Promise 객체를 사용하는 경우가 더 많을 것입니다.

REST API를 호출할 때 사용되는 브라우저 내장 함수인 fetch()가 대표적인데요. (NodeJS 런타임에서는 node-fetch 모듈을 설치해야 사용 가능) fetch() 함수는 API의 URL을 인자로 받고, 미래 시점에 얻게될 API 호출 결과를 Promise 객체로 리턴합니다. network latency 때문에 바로 결과값을 얻을 수 없는 상황이므로 위에서 설명한 Promise를 사용 목적에 정확히 부합합니다.

Promise 객체의 then() 메소드는 결과값을 가지고 수행할 로직을 담은 콜백 함수를 인자로 받습니다. 그리고 catch() 메서드는 예외 처리 로직을 담은 콜백 함수를 인자로 받습니다.

예를 들어, fetch() 함수를 이용해서 어떤 서비스의 API를 호출 후, 정상 응답 결과를 출력해보겠습니다.

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => console.log("response:", response))
  .catch((error) => console.log("error:", error));
  • 결과
response: Response {type: "cors", url: "https://jsonplaceholder.typicode.com/posts/1", redirected: false, status: 200, ok: true, …}

인터넷 상에서 유효한 URL을 fetch() 함수의 인자로 넘겼기 때문에 예외가 발생하지 않고 then()에 인자로 넘긴 콜백 함수가 호출되어 상태 코드 200의 응답이 출력되었습니다.

이번에는 fetch() 함수의 인자로 아무 URL을 넘기지 않아보겠습니다.

fetch()
  .then((response) => console.log("response:", response))
  .catch((error) => console.log("error:", error));
  • 결과
error: TypeError: Failed to execute 'fetch' on 'Window': 1 argument required, but only 0 present.
    at main-sha512-G7qgGx8Wefk5JskAfRw2DfBPNPQTxDC23DcZ+KQTmNoSr2S6pZ3IJgYs1ThvLvvH7uI_KhycDx_FIDNlu5KhOw==.bundle.js:9070
    at <anonymous>:1:1

이번에는 catch() 메서드의 인자로 넘긴 콜백 함수가 호출되어 에러 정보가 출력되었음을 알 수 있습니다.

이와 같이 Promise는 then()과 catch() 메서드를 통해서 동기 처리 코드에서 사용하던 try-catch 블록과 유사한 방법으로 비동기 처리 코드를 작성할 수 있도록 해줍니다.

Promise의 메서드 체이닝(method chaining)

then()과 catch() 메서드는 또 다른 Promise 객체를 리턴합니다. 그리고 이 Promise 객체는 인자로 넘긴 콜백 함수의 리턴값을 다시 then()과 catch() 메서드를 통해 접근할 수 있도록 해줍니다. 다시 말하면 then()과 catch() 메서드는 마치 사슬처럼 계속 연결하여 연쇄적으로 호출을 할 수 있습니다.

예를 들어, 이전 섹션의 fetch() 메서드 사용 예제에서 단순히 응답 결과가 아닌 응답 전문을 json 형태로 출력하고 싶은 경우에는 then() 메서드를 추가로 연결해주면 됩니다.

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => response.json())
  .then((post) => console.log("post:", post))
  .catch((error) => console.log("error:", error));
  • 결과
post: {userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit↵suscipit recusandae consequuntur …strum rerum est autem sunt rem eveniet architecto"}

또 다른 예로, 위의 포스팅를 작성한 userId 1을 가진 유저의 데이터가 필요한 경우, 다음과 같이 추가 메서드 체이닝을 할 수 있습니다. 3번째 줄의 콜백 함수는, post 객체에서 userId 필드만 추출하여 리턴하고 있으며, 4번째 줄의 콜백 함수는, 이 userId를 가자고 유저 상세 조회를 위한 API의 URL을 만들어서 리턴하고 있으며, 5번째 줄의 콜백 함수는, 이 url을 가지고 fetch() 함수를 호출하여 새로운 Promise를 리턴하고 있습니다.

 
fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => response.json())
  .then((post) => post.userId)
  .then((userId) => "https://jsonplaceholder.typicode.com/users/" + userId)
  .then((url) => fetch(url))
  .then((response) => response.json())
  .then((user) => console.log("user:", user))
  .catch((error) => console.log("error:", error));
  • 결과
user: {id: 1, name: "Leanne Graham", username: "Bret", email: "Sincere@april.biz", address: {…}, …}

여기서 주의하실 점은 then()과 catch()의 인자로 넘긴 콜백 함수는 3, 4번째 줄처럼 일반 객체를 리턴하든 5번째 줄처럼 Promise 객체를 리턴하든 크게 상관이 없다는 것입니다. 왜냐하면 일반 객체를 리턴할 경우, then()과 catch() 메소드는 항상 그 객체를 얻을 수 있는 Promise 객체를 리턴하도록 되어 있기 때문입니다.

마치면서

Promise을 사용하면서 자연스럽게 발생하는 이러한 코딩 스타일은 매우 흔하게 볼 수 있으며 자바스크립트 개발자들 사이에서도 호불호가 갈리기도 합니다. (개인적으로는 그래도 적어도 계속적인 들여쓰기를 강요하는 콜백 함수를 인자로 넘기는 방법보다는 Promise를 사용하는 편이 낫다고 생각합니다.)

사실 최근에는 이러한 Promise를 이용해서 계속해서 메서드 체이닝하는 코딩 스타일은 자바스크립트의 async/await 키워드를 사용하는 방식으로 대체되고 있는 추세입니다. 다음 포스팅에서는 이 async/await 키워드를 사용해서 어떻게 자바스크립트 비동기 처리 코드를 개선할 수 있는지 알아보록 하겠습니다.

전체 코드

https://gist.github.com/DaleSeo/c366c0a8c1416a89adb1f64975be8f64

 

JS Async Promise

JS Async Promise. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

 

[자바스크립트] 비동기 처리 3부 - async/await

이전 두 개의 포스팅를 통해서 기존에 자바스크립트로 어떻게 비동기 처리 코드를 작성해왔는지에 대해서 살펴보았습니다. 이번 포스팅에서는 좀 더 개선된 방식으로 비동기 처리를 할 수있도록 도와주는 async/await에 대해서 알아보도록 하겠습니다. async/await를 제대로 시용하려면 Callback과 Promise에 대한 이해가 무엇보다 중요하오니 아래 포스팅도 참고 바라겠습니다.

Promise를 통한 비동기 코딩

먼저 Promise를 통해서 어떻게 비동기 처리를 하는지 간단하게 리뷰해보겠습니다. 어떤 원격 REST API를 호출을 하여 게시물 작성자의 이름을 리턴하는 함수를 작성하고 그 함수를 호출해보았습니다.

function fetchAuthorName(postId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
    .then((response) => response.json())
    .then((post) => post.userId)
    .then((userId) => {
      return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
        .then((response) => response.json())
        .then((user) => user.name);
    });
}

fetchAuthorName(1).then((name) => console.log("name:", name));
  • 출력
name: Leanne Graham

브라우저 내장 함수인 fetch()를 호출하여 Promise 객체를 리턴받은 후에, Method Chaning 기법을 통해 then() 메서드를 연쇄적으로 호출하고 있습니다. 마치 리눅스의 파이프(|) 키워드처럼 then() 메서드는 바로 이전 then() 메서드의 출력값을 입력값으로 사용하여 새로운 출력값을 만들고, 바로 다음 then() 메서드의 입력값으로 넘겨줍니다.

fetchAuthorName() 함수 자체도 결국 게시물 작성자의 이름을 얻을 수 있는 Promsie 객체를 리턴하기 때문에, 호출할 때도 then() 메서드를 사용하여 게시물 작성자의 이름을 출력해야 합니다.

문제점

Promise를 사용하면 자연스럽게 위와 같은 코딩 스타일로 비동기 처리를 하게되는데 여기에는 여러가지 문제점이 있습니다.

  1. 디버깅

위 코드의 8번째 줄을 아래처럼 에러가 발생하도록 의도적으로 수정 후에 코드를 실행을 해보면 다음과 같은 에러 메세지를 보게 됩니다.

.then(user => user1.name);
  • 결과
ReferenceError: user1 is not defined
    at fetch.then.then.then.then.then (<anonymous>:7:29)

동일한 이름의 메서드인 then()을 연쇄적으로 호출하고 있어서 도대체 몇 번째 then()에서 문제가 발생한 건지 Stack Trace을 보더라도 혼란스러울 수 있습니다.

 

또한 then() 메서드 호출 부에 break point를 걸고 디버거를 돌리면, 위 코드와 같이 화살표 함수로 한 줄짜리 콜백 함수를 넘긴 경우에는 코드 실행이 break point에서 멈추지 않기 때문에 디버깅이 상당히 불편합니다.

  1. 예외 처리

Promise를 사용하면 try/catch 대신에 catch() 메서드를 사용하여 예외 처리를 해야합니다. 이 부분이 비동기 코드만 있을 때는 그렇게 거슬리지 않는데, 동기 코드와 비동기 코드가 섞여 있을 경우 예외 처리가 난해해지거나 예외 처리를 누락하는 경우가 생기기 쉽습니다.

  1. 들여쓰기

실제 프로젝트에서는 샘플 코드와 같이 간단한 구조가 아닌 복잡한 구조의 비동기 처리 코드를 작성하게 됩니다. 따라서, then() 메서드의 인자로 넘기는 콜백 함수 내에서 조건문이나 반복문을 사용하거나 여러 개의 Promise를 병렬로 또는 중첩해서 호출해야하는 경우들이 발생하게 됩니다. 이럴 경우, 다단계 들여쓰기를 해야할 확률이 높아지며 코드 가독성은 점점 떨어지게 됩니다.

async/await 키워드를 통한 비동기 코딩

Promise의 이러한 불편한 점들을 해결하기 위해 ES7(ES2017)에서 async/await 키워드가 추가되었습니다. async/await 키워드를 사용하면 비동기 코드를 마치 동기 코드처럼 보이게 작성할 수 있습니다.

위의 샘플 코드를 async/await 키워드를 이용하여 재작성 해보겠습니다.

async function fetchAuthorName(postId) {
  const postResponse = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );
  const post = await postResponse.json();
  const userId = post.userId;
  const userResponse = await fetch(
    `https://jsonplaceholder.typicode.com/users/${userId}`
  );
  const user = await userResponse.json();
  return user.name;
}

fetchAuthorName(1).then((name) => console.log("name:", name));
  • 출력
name: Leanne Graham

달라진 점을 찾아 보면 먼저 함수 선언부를 보면 async 키워드가 function 앞에 붙었다는 것을 알 수 있습니다. 그리고 Promise를 리턴하는 모든 비동기 함수 호출부 앞에는 await 키워드가 추가되었습니다.

await 키워드는 async 키워드가 붙어있는 함수 내부에서만 사용할 수 있으며 비동기 함수가 리턴하는 Promise로 부터 결과값을 추출해줍니다. 즉, await 키워드를 사용하면 일반 비동기 처리처럼 바로 실행이 다음 라인으로 넘어가는 것이 아니라 결과값을 얻을 수 있을 때까지 기다려줍니다. 따라서 일반적인 동기 코드 처리와 동일한 흐름으로 (함수 호출 후 결과값을 변수에 할당하는 식으로) 코드를 작성할 수 있으며, 따라서 코드를 읽기도 한결 수월해집니다.

 

한가지 주의할 점은 async 키워드가 붙어있는 함수를 호출하면 명시적으로 Promise 객체를 생성하여 리턴하지 않아도 Promise 객체가 리턴됩니다. 따라서 호출부를 보시면 Promise 객체를 사용했던 것 동일한 방식으로 then() 메서드를 통해서 결과값을 출력하고 있습니다.

하지만 만약 이 호출부가 또 다른 async 키워드가 붙어있는 함수의 내부에 있다면 동일한 방식으로 await 키워드를 사용하여 Promise가 제공할 값에 바로 접근할 수 있습니다.

async function printAuthorName(postId) {
  const name = await fetchAuthorName(postId);
  console.log("name:", name);
}

printAuthorName(1);

예외 처리

동기/비동기 구분없이 try/catch로 일관되게 예외 처리를 할 수 있는 부분도 async/await를 사용해서 코딩했을 때의 큰 이점 중 하나입니다.

async function fetchAuthorName(postId) {
  const postResponse = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );
  const post = await postResponse.json();
  const userId = post.userId;

  try {
    const userResponse = await fetch(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    const user = await userResponse.json();
    return user.name;
  } catch (err) {
    console.log("Faile to fetch user:", err);
    return "Unknown";
  }
}

fetchAuthorName(1).then((name) => console.log("name:", name));

마치면서

이상으로 async/await를 이용해서 좀 더 깔끔하게 비동기 코딩을 하는 방법에 대해서 알아보았습니다. 앞으로 가면 갈수록 async/await를 사용한 자바스크립트 코드를 볼 일이 많아질거라 생각합니다. 이 번 기회에 async/await를 마스터하시고 적극적으로 활용해보셨으면 좋겠습니다.

전체 코드

https://gist.github.com/DaleSeo/0c84a7361d55311b10558bd55e25f2e2

반응형

import, export

JavaScript, ECMAScript
반응형

# 참조 : https://www.youtube.com/watch?v=WUirHxOBXL4

 

아래 4가지 import 문장의 차이 이해

import { Module } from './module';
import   Module   from './module';
import * as myModule from './module';
const Module = await import('./module');

 

math.js # export main.js # import 추가설명
1) 개별 export 방식
  export const plus = (a, b) => a+b;
  export const minus = (a, b) => a-b;
  export const divide = (a, b) => a/b;
1) 개별 import 방식 (=named import)
import {plus} from "./math";
plus(2, 2);
혹은
import {plus as add} from "./math";
add (2, 2);
- {} 를 꼭 써야 하는 방식을 named import 라 함. 
- plus 란 이름 동일해야 함.
- 바꾸려면 as 로 변경 가능
2) 통합 export 방식
  export const plus = (a, b) => a+b;
  export const minus = (a, b) => a-b;
  export const divide = (a, b) => a/b;
  export default {plus, minus, divide};
2) 통합 import 방식
import myMath from "./math";

myMath.plus(2, 2);
- "export default"를 import 시 {} 를 안 쓴다.
- {} 안쓴 import "export default"  통째로 import 하는 .
3) 혼합 export 방식
const connectToDB = () => {/*code*/}
export const getUrl = () => {/*code*/}
3) 혼합 import 방식
import connect, {getUrl} from "./db";
- 개별 import 와 통합 import 를 혼용할 때.
4) 개별 일괄 export 방식
  const plus = (a, b) => a+b;
  const minus = (a, b) => a-b;
  const divide = (a, b) => a/b;
4) 개별 일괄 import 방식
import * as myMath from "./math";
myMath.plus(2, 2);
- export 가 없음.
  5) Dynamic import 방식 1
function doMath() {
    import("./math")
    .then(math => math.plus(2,2));
}

btn.addEventListener("click", doMath);

 
  5) Dynamic import 방식 2
async function doMath() {
    const math= await import("./math");
    math.plus(2,2);
}

btn.addEventListener("click", doMath);
 

 

반응형

CORS.ajax.fetch.어떻게 해도 CORS 가 발생할 경우

JavaScript, ECMAScript
반응형

서버쪽에 널리 알려진 방식으로 CORS 세팅해후에,

 

ajax 호출시에 아래 추가

withCredentials: true,
Credentials: “include”

referrerPolicy: ‘origin-when-cross-origin’
mode: “cors”

 

추가 발생 가능 이유 1 ) Cookie 가 너무 길면 발생할 수 있다.

추가 발생 가능 이유 2 ) 웹서버나 와스 서버 둘중 한군데만 설정해야 한다. 아니면 multiple 에러가 발생한다.

 

반응형

'JavaScript, ECMAScript' 카테고리의 다른 글

Callback, Promise, Async/Await  (0) 2022.04.04
import, export  (0) 2021.09.28
문법 기본  (0) 2020.10.07
자바스크립트 역사.  (0) 2020.09.21
Count, 카운트  (0) 2020.03.09