Day +25

비트·2023년 5월 16일
0

CodeStates

목록 보기
26/54
post-thumbnail

비동기

동기(synchronous)

  • JavaScript의 동기 처리란, 특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것을 의미

비동기(asynchronous)

  • JavaScript의 비동기 처리는 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드들을 수행하는 것 을 의미

JavaScript의 작동원리

  • JavaScript는 싱글 스레드 기반으로 동작하는 언어
    • 따라서 동기적으로 작동
  • 그러나 방금까지 JavaScript에서도 비동기 처리가 가능하다고 했는데. 어떻게 된 걸까?
    • JavaScript가 작동하는 환경(런타임)에서 비동기 처리를 도와주기 때문에 특별한 작업 없이 비동기 처리를 할 수 있는 것


타이머 관련 API

setTimeout(callback, millisecond)

  • 일정 시간 후에 함수를 실행
    • 매개변수(parameter): 실행할 콜백 함수, 콜백 함수 실행 전 기다려야 할 시간 (밀리초)
    • return 값: 임의의 타이머 ID
    • setTimeout(function () {
        console.log('1초 후 실행');
       }, 1000);
       // 123

clearTimeout(timerId)

  • setTimeout 타이머를 종료
    • 매개변수(parameter): 타이머 ID
    • return 값: 없음
    • const timer = setTimeout(function () {
        console.log('10초 후 실행');
       }, 10000);
       clearTimeout(timer);
       // setTimeout이 종료됨.

setInterval(callback, millisecond)

  • 일정 시간의 간격을 가지고 함수를 반복적으로 실행
    • 매개변수(parameter): 실행할 콜백 함수, 반복적으로 함수를 실행시키기 위한 시간 간격 (밀리초)
    • return 값: 임의의 타이머 ID
    • setInterval(function () {
        console.log('1초마다 실행');
       }, 1000);
       // 345

clearInterval(timerId)

  • setInterval 타이머를 종료
    • 매개변수: 타이머 ID
    • return 값: 없음
    • const timer = setInterval(function () {
        console.log('1초마다 실행');
       }, 1000);
       clearInterval(timer);
       // setInterval이 종료됨.


Callback

  • 비동기로 작동하는 코드를 제어할 수 있는 방법에 대해 잘 알고 있어야 한다.
    • 여러 방법 중 하나는 Callback 함수를 활용하는 방법이 있다.
    • Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있습니다.
      • 즉, 비동기를 동기화할 수 있다는 의미
  • const printString = (string, callback) => {
       setTimeout(function () {
         console.log(string);
         callback();
       }, Math.floor(Math.random() * 100) + 1);
     };
    
     const printAll = () => {
       printString('A', () => {
         printString('B', () => {
           printString('C', () => {});
         });
       });
     };
    
     printAll();
    
     console.log(
       `아래와 같이 Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있습니다!`
     );
     
     // 출력 ↓
     // 아래와 같이 Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있습니다!
     // A
     // B
     // C

Callback Hell

  • Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있지만 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Callback Hell이 발생하는 단점이 있다.

    • const printString = (string, callback) => {
         setTimeout(function () {
           console.log(string);
           callback();
         }, Math.floor(Math.random() * 100) + 1);
       };
      
       const printAll = () => {
         printString('A', () => {
           printString('B', () => {
             printString('C', () => {
               printString('D', () => {
                 printString('E', () => {
                   printString('F', () => {
                     printString('G', () => {
                       printString('H', () => {
                         printString('I', () => {
                           printString('J', () => {
                             printString('K', () => {
                               printString('L', () => {
                                 printString('M', () => {
                                   printString('N', () => {
                                     printString('O', () => {
                                       printString('P', () => {});
                                     });
                                   });
                                 });
                               });
                             });
                           });
                         });
                       });
                     });
                   });
                 });
               });
             });
           });
         });
       };
      
       printAll();
      
       console.log(
         `아래와 같이 Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있지만 코드가 길어질 수록 복잡해지고 가독성이 낮아지는 Callback Hell이 발생하는 단점이 있습니다.`
       );
       
       // 출력 ↓
       // 아래와 같이 Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있지만 코드가 길어질 수록 복잡해지고 가독성이 낮아지는 Callback Hell이 발생하는 단점이 있습니다.
       // A
       // B
       // ...
       // O
       // P (차례대로 호출)


Promise

new Promise

  • Promise는 class이기 때문에 new 키워드를 통해 Promise 객체를 생성
  • 또한 Promise는 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달받는데 이 콜백 함수는 resolve, reject 함수를 인수로 전달받는다.
  • Promise 객체가 생성되면 executor는 자동으로 실행되고 작성했던 코드들이 작동.

  • 코드가 정상적으로 처리가 되었다면 resolve 함수를 호출하고 에러가 발생했을 경우에는 reject 함수를 호출하면 된다.

    •   let promise = new Promise((resolve, reject) => {
      	// 1. 정상적으로 처리되는 경우
      	// resolve의 인자에 값을 전달할 수도 있습니다.
      	resolve(value);
      
      	// 2. 에러가 발생하는 경우
      	// reject의 인자에 에러메세지를 전달할 수도 있습니다.
      	reject(error);
      });

Promise 객체의 내부 프로퍼티

  • new Promise가 반환하는 Promise 객체는 state, result 내부 프로퍼티를 갖는다.
  • 하지만 직접 접근할 수 없고 .then, .catch, .finally의 메서드를 사용해야 접근이 가능.

State

  • 기본 상태는 pending(대기)입니다. 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동했다면 fulfilled(이행)로 변경이 되고, 에러가 발생했다면 rejected(거부)가 된다.

Result

  • 처음은 undefined입니다. 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동하여 resolve(value)가 호출되면 value로, 에러가 발생하여 reject(error)가 호출되면 error로 변한다.

then, catch, finally

Then

  • executor에 작성했던 코드들이 정상적으로 처리가 되었다면 resolve 함수를 호출하고 .then 메서드로 접근할 수 있다.
  • 또한 .then 안에서 리턴한 값이 PromisePromise의 내부 프로퍼티 result를 다음 .then 의 콜백 함수의 인자로 받아오고, Promise가 아니라면 리턴한 값을 .then 의 콜백 함수의 인자로 받아올 수 있다.
  • 아래의 .thenPromise chaining의 예시를 살펴보면서 동작 방식을 확인해 보자.

    • let promise = new Promise((resolve, reject) => {
         resolve("성공");
       });
      
       promise.then(value => {
          console.log(value);
          // "성공"
       })

Catch

  • executor에 작성했던 코드들이 에러가 발생했을 경우에는 reject 함수를 호출하고 .catch 메서드로 접근할 수 있다.

    • let promise = new Promise(function(resolve, reject) {
         reject(new Error("에러"))
       });
      
       promise.catch(error => {
          console.log(error);
          // Error: 에러
       })

Finally

  • executor에 작성했던 코드들의 정상 처리 여부와 상관없이 .finally 메서드로 접근할 수 있다.

    • let promise = new Promise(function(resolve, reject) {
          resolve("성공");
       });
      
       promise
       .then(value => {
          console.log(value);
          // "성공"
       })
       .catch(error => {
          console.log(error);
       })
       .finally(() => {
          console.log("성공이든 실패든 작동!");
          // "성공이든 실패든 작동!"
       })

Promise chaining

  • Promise chaining가 필요하는 경우는 비동기 작업을 순차적으로 진행해야 하는 경우.
  • Promise chaining이 가능한 이유는 .then, .catch, .finally 의 메서드들은 Promise를 리턴하기 때문.
  • 따라서 .then을 통해 연결할 수 있고, 에러가 발생할 경우 .catch 로 처리하면 된다.

    • let promise = new Promise(function(resolve, reject) {
        resolve('성공');
        ...
       });
      
       promise
      .then((value) => {
        console.log(value);
        return '성공';
      })
      .then((value) => {
        console.log(value);
        return '성공';
      })
      .then((value) => {
        console.log(value);
        return '성공';
      })
      .catch((error) => {
        console.log(error);
        return '실패';
      })
      .finally(() => {
        console.log('성공이든 실패든 작동!');
      });

Promise.all()

  • Promise.all()은 여러 개의 비동기 작업을 동시에 처리하고 싶을 때 사용
    • const promiseOne = () => new Promise((resolve, reject) => setTimeout(() => resolve('1초'), 1000));
      const promiseTwo = () => new Promise((resolve, reject) => setTimeout(() => resolve('2초'), 2000));
      const promiseThree = () => new Promise((resolve, reject) => setTimeout(() => resolve('3초'), 3000));
    • 인자로는 배열을 받는다. 해당 배열에 있는 모든 Promise에서 executor 내 작성했던 코드들이 정상적으로 처리가 되었다면 결과를 배열에 저장해 새로운 Promise를 반환.

  • 앞서 배운 Promise chaining을 사용했을 경우는 코드들이 순차적으로 동작되기 때문에 총 6초의 시간이 걸리게 된다. 또한, 같은 코드가 중복되는 현상도 발생
    • // 기존
      const result = [];
      promiseOne()
        .then(value => {
          result.push(value);
          return promiseTwo();
        })
        .then(value => {
          result.push(value);
          return promiseThree();
        })
        .then(value => {
          result.push(value);
         console.log(result);  
      	 // ['1초', '2초', '3초']
        })
  • 이러한 문제들을 Promise.all()을 통해 해결할 수 있습니다. Promise.all()은 비동기 작업들을 동시에 처리.
  • 따라서 3초 안에 모든 작업이 종료됩니다. 또한 Promise chaining로 작성한 코드보다 간결해진 것을 확인할 수 있다.
    • // promise.all
      Promise.all([promiseOne(), promiseTwo(), promiseThree()])
        .then((value) => console.log(value))
        // ['1초', '2초', '3초']
        .catch((err) => console.log(err));
  • 추가적으로 Promise.all()은 인자로 받는 배열에 있는 Promise 중 하나라도 에러가 발생하게 되면 나머지 Promise의 state와 상관없이 즉시 종료.
  • 아래의 예시와 같이 1초 후에 에러가 발생하고 Error: 에러1이 반환된 후로는 더 이상 작동하지 않고 종료
    • Promise.all([
      	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러1'))), 1000),
      	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러2'))), 2000),
      	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러3'))), 3000),
      ])
      	.then((value) => console.log(value))
        .catch((err) => console.log(err));
      	// Error: 에러1

Promise Hell

  • Promise를 통해 비동기 코드의 순서를 제어할 수 있지만 Callback 함수와 같이 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생하는 단점이 있다.

    • const printString = (string) => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(string);
          }, Math.floor(Math.random() * 100) + 1);
        });
      };
      
       const printAll = () => {
        printString('A').then((value) => {
          console.log(value);
      
          printString('B').then((value) => {
            console.log(value);
      
            printString('C').then((value) => {
              console.log(value);
      
              printString('D').then((value) => {
                console.log(value);
      
                printString('E').then((value) => {
                  console.log(value);
      
                  printString('F').then((value) => {
                    console.log(value);
      
                    printString('G').then((value) => {
                      console.log(value);
      
                      printString('H').then((value) => {
                        console.log(value);
      
                        printString('I').then((value) => {
                          console.log(value);
      
                          printString('J').then((value) => {
                            console.log(value);
      
                            printString('K').then((value) => {
                              console.log(value);
      
                              printString('L').then((value) => {
                                console.log(value);
      
                                printString('M').then((value) => {
                                  console.log(value);
      
                                  printString('N').then((value) => {
                                    console.log(value);
      
                                    printString('O').then((value) => {
                                      console.log(value);
      
                                      printString('P').then((value) => {
                                        console.log(value);
                                      });
                                    });
                                  });
                                });
                              });
                            });
                          });
                        });
                      });
                    });
                  });
                });
              });
            });
          });
        });
      };
      
       printAll();
      
       console.log(
        `아래와 같이 Promise를 통해 비동기 코드의 순서를 제어할 수 있지만 Callback 함수와 같이 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생하는 단점이 있습니다.`
      );
      
        // 출력 ↓
        // 아래와 같이 Promise를 통해 비동기 코드의 순서를 제어할 수 있지만 Callback 함수와 같이 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생하는 단점이 있습니다.
        // A
        // B
        // ...
        // O
        // P

Async/Await

  • JavaScript는 ES8에서 async/await키워드를 제공하였다.
  • 이를 통해 복잡한 Promise 코드를 간결하게 작성할 수 있게 되었다.
  • 사용법은 간단하다.
    • 함수 앞에 async 키워드를 사용하고 async 함수 내에서만 await 키워드를 사용하면 됩니다.
    • 이렇게 작성된 코드는 await 키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작하게 된다.
  • // 함수 선언식
    async function funcDeclarations() {
    	await 작성하고자 하는 코드
    	...
    }
    
     // 함수 표현식
    const funcExpression = async function () {
    	await 작성하고자 하는 코드
    	...
    }
    
     // 화살표 함수
    const ArrowFunc = async () => {
    	await 작성하고자 하는 코드
    	...
    }



Node.js

  • 브라우저에서 사용할 수 있는 비동기 흐름은 타이머 혹은 DOM 이벤트와 관련된 상황으로 다소 한정적이지만, Node.js의 경우 많은 API가 비동기로 작성되어 있다.

  • Node.js : 비동기 이벤트 기반 JavaScript 런타임

    • Node.js 모듈을 사용하는 방법을 먼저 학습하고, 이를 통해 비동기 상황을 연출하고 연습하자.
    • 모듈이란?

      • 건축으로부터 비롯된 모듈이라는 단어는, 어떤 기능을 조립할 수 있는 형태로 만든 부분
      • 그중 fs(File System) 모듈은, PC의 파일을 읽거나 저장하는 등의 일을 할 수 있게 도와준다.

Node.js 내장 모듈을 사용하는 방법

  • 개발자는 자신이 이해하는 범위만큼 모듈을 사용할 수 있다.
    • 예를 들어, DNS에 대한 지식을 알고 있다면, DNS 모듈 사용법 문서에서 관련 메서드를 사용할 수 있다.
    • DNS : 파일 시스템 모듈이 파일을 읽거나 저장하는 기능을 구현할 수 있도록 돕는다.

  • 모든 모듈은 모듈을 사용하기 위해 불러오는 과정이 필요하다.
    • 브라우저에서 다른 파일을 불러올 때에는 <script> 태그를 이용했다.
    • // HTML에서 JavaScript 파일을 불러오는 script 태그
       <script src="불러오고싶은_스크립트.js"></script>
  • Node.jsJavaScript 코드 가장 상단에 require 구문을 이용하여 다른 파일을 불러온다.
    • // Node.js에서 다른 파일을 불러오는 require 구문
      const fs = require('fs'); // 파일 시스템 모듈을 불러옵니다
      const dns = require('dns'); // DNS 모듈을 불러옵니다

3rd-party 모듈을 사용하는 방법

  • 서드 파티 모듈(3rd-party module)
    • 해당 프로그래밍 언어에서 공식적으로 제공하는 빌트인 모듈(built-in module)이 아닌 모든 외부 모듈을 일컫습니다.
  • 예를 들어, Node.js에서 underscore는 Node.js 공식 문서에 없는 모듈이기 때문에 서드 파티 모듈입니다.

    • underscore와 같은 서드 파티 모듈을 다운로드하기 위해서는 npm을 사용해야 합니다.
      • underscore : 내장 개체를 확장하지 않고 유용한 함수형 프로그래밍 도우미를 제공하는 JavaScript 라이브러리
      • // 터미널에서 다음과 같이 입력해 underscore를 설치할 수 있습니다.
        npm install underscore
    • 이제 node_modules에 underscore가 설치되었다.
      • 이제 Node.js 내장 모듈을 사용하듯 require 구문을 통해 underscore를 사용할 수 있다.
      • // Node.js의 3rd-party underscore를 사용할 수 있습니다.
        const _ = require('underscore');

fs.readFile을 통해 알아보는 Node.js 공식문서 가이드

  • 메서드 fs.readFile은 로컬에 존재하는 파일을 읽어온다.
  • 16.x 버전 기준으로, fs.readFile의 공식 API 문서에 안내되어 있는 항목을 설명한다.
  • 공식 문서와 이 내용을 동시에 놓고 보면, 공식 문서의 구성을 이해하는 데 도움이 된다.

fs.readFile(path[, options], callback)

  • 메서드 fs.readFile은 비동기적으로 파일 내용 전체를 읽는다.
  • 이 메서드를 실행할 때에는 전달인자 세 개를 받습니다.

path \<string> | \<Buffer> | \<URL> | \<integer>

path

  • path에는 파일 이름을 전달인자로 받습니다.
  • 네 가지 종류의 타입을 넘길 수 있지만 일반적으로 문자열(<string>)의 타입을 받습니다.
    • //  '/etc/passwd' 파일을 불러오는 예제
      fs.readFile('/etc/passwd', ..., ...)

options \<Object> | \<string>

options

  • 대괄호로 감싼 두 번째 전달인자 options는 넣을 수도 있고, 넣지 않을 수도 있습니다. 대괄호는 선택적 전달인자를 의미한다.
  • options는 문자열 또는 객체 형태로 받을 수 있다.

  • 문자열로 전달할 경우 인코딩을 받는다.

  • 밑의 예제에서는 'utf8'을 두 번째 전달인자로 받는 것을 확인할 수 있다.

    • // /etc/passwd 파일을 'utf8'을 사용하여 읽습니다.
      
      // 두 번째 전달인자 options에 문자열을 전달한 경우
      fs.readFile('/etc/passwd', 'utf8', ...);
    • // 두 번째 전달인자 options에 객체를 전달한 경우
       let options = {
         encoding: 'utf8', // utf8 인코딩 방식으로 엽니다
         flag: 'r' // 읽기 위해 엽니다
       }
      
       // /etc/passwd 파일을 options를 사용하여 읽습니다.
       fs.readFile('/etc/passwd', options, ...) 

callback \<Function>

callback

  • err \<Error> | \<AggregateError>
  • data \<string> | \<Buffer>
  • 콜백 함수를 전달합니다.
  • 파일을 읽고 난 후에 비동기적으로 실행되는 함수입니다.
  • 콜백 함수에는 두 가지 매개변수가 존재한다.
  • 에러가 발생하지 않으면 err는 null 이 되며, data에 문자열이나 Buffer라는 객체가 전달된다. data는 파일의 내용이다.
profile
Drop the Bit!

0개의 댓글