[태그:] 자바스크립트

  • [JavaScript] 문자열이 공백인지 체크

    [JavaScript] 문자열이 공백인지 체크

    문자열이 공백인지 체크하는 함수

    검색창 구현하다 필요해서 구글링해서 줍줍

    /**
     * 정규표현식을 활용하여 문자열이 공백일경우 'true' 반환
     * @param {string} str 
     * @returns {boolean} 공백일경우 'true' 아닐경우 'false'
     */
    function checkSpaceStr(str) {
    
        let bln = false;
        let blankPattern = /^\s+|\s+$/g;
        if (str.replace(blankPattern, '') === '') {
            bln = true;
        }
        return bln;
    }
    

    References

    회원가입에서 공백, 특수문자 검사하기

  • [JavaScript] 함수형 프로그래밍의 순수 함수

    [JavaScript] 함수형 프로그래밍의 순수 함수

    함수형 프로그래밍

    부수 효과를 없애고 순수 함수를 만들어 모듈화 수준을 높이는 프로그래밍 패러다임

    • 부수 효과: 외부 상태를 변경하거나 함수로 들어온 인자 상태를 변경하는 것
    • 순수 함수
      • 동일한 입력에 대해 항상 동일한 출력을 반환하는 함수
      • 외부의 상태를 변경하거나 영향을 받지 않는 함수

    순수한 함수

    function func(a, b) {
      return a + b;
    }
    
    console.log(func(2, 2)); // 4
    

    func 함수는 순수하다. 언제나 이 함수를 수백번 실행시켜도 입력값이 2, 2면 출력값이 4로 동일하기 때문이다. 또한 이 함수는 외부의 값에 영향을 주거나 받지 않는다.

    순수하지 않은 함수

    let c = 1;
    
    function func(a, b) {
      return a + b + c;
    }
    
    console.log(func(2, 2)); // 5
    
    c = 2; // c 값이 변경됨
    
    console.log(func(2, 2)); // 6
    

    위 함수는 외부 값인인 c에 영향을 받기 때문에 순수함수가 아니다. c가 변하면 동일한 입력에 대해 출력이 다르기 때문이다.

    let c = 1;
    
    function func(a, b) {
      c += 1; // 외부의 값에 변화를 주며, 이를 부수효과라 함
      return a + b;
    }
    
    func(2, 2); // 함수 실행
    
    console.log(c); // c 값이 2로 변화됨
    

    위 함수도 함수가 실행되면 외부값인 c를 변경시키기 때문에 순수 함수가 아니며, 이를 부수 효과라 한다.

    let obj = {
      a: 1
    };
    
    function func(obj) {
      return obj.b = 1; // 인자로 받은 객체에 b 값을 추가하여 리턴
    }
    
    func(obj);
    
    console.log(obj); // { a: 1, b: 1 }
    

    객체의 경우도 살펴보자. 위 함수도 외부 obj 객체에 b가 추가되었기 때문에 순수함수가 아니다.

    let obj = {
      a: 1
    };
    
    function func(obj) {
      return obj; // 인자로 받은 객체를 그대로 리턴
    }
    
    let obj2 = func(obj);
    

    위의 경우는 어떨까? 함수 안에서는 객체를 받고 아무런 변화를 주지 않고 리턴하였으며, 새로운 변수에 리턴된 객체를 할당했다. 위 함수에서는 객체에 아무런 변화를 주지 않았으니 순수 함수라고 할 수 있을까?

    let obj = {
      a: 1
    };
    
    function func(obj) {
      return obj; // 인자로 받은 객체를 그대로 리턴
    }
    
    let obj2 = func(obj); // 새로운 변수에 리턴된 객체를 할당
    
    obj2.a = 2; // 새로운 객체 obj2의 a 값을 변경
    
    console.log(obj2); // { a: 2 }
    console.log(obj); // { a: 2 }
    

    정답은 아니다. func 함수를 실행하여 새로운 변수에 리턴받은 객체를 할당했으며, 새로운 객체 obj2a 값을 변경하였다. 그랬더니 기존의 obj 객체의 값도 변경이 되었다. 바로 객체의 참조(주소) 값도 같이 복사되어 새롭게 만든 obj2가 변화함에 따라 기존의 obj 객체도 변경되기 때문이다. 이처럼 함수 내에서 직접 값을 변경하지 않았더라도 함수에 들어온 인자값을 그대로 사용하면 순수 함수가 아니다.

    let obj = {
      a: 1
    };
    
    function func(obj) {
    
      // 객체의 값만 참조하여 새로운 객체를 리턴
      return {
        a: obj.a,
        b: 2
      };
    }
    
    let obj2 = func(obj);
    
    obj2.a = 2;
    
    console.log(obj2); // { a: 2, b: 2 }
    console.log(obj); // { a: 1 }
    

    위에서는 인자로 받은 객체를 직접 사용하지 않고 obj.a 값만 참조해서 새로운 객체를 생성하여 리턴하고 있다. 이럴 경우는 참조(주소)값이 복사가 안되기 때문에 obj2 객체의 값이 변경되도 obj의 값이 변경되지 않는다. 그러므로 위 함수는 순수 함수라 할 수 있다.

    결론

    모든 함수가 순수 할수일 수는 없다. 모든 함수가 순수 함수라면 외부의 어떤 데이터에도 변형을 주지 않기 때문에 프로그램은 구동되지 않을 것이다. 단지, 이런 스타일로 코딩하는것이 함수형 프로그래밍의 패러다임 이며, 이 패러다임의 목적은 외부 상태의 변화를 최소함으로 유지하고, 함수 실행 결과 예측을 용이하게 하여 버그 발생 가능성을 줄이는 것에 목적이 있다.

    [번역] JavaScript 함수형 프로그래밍 3단계로 설명하기
    순수 함수란 무엇인가요… 별거 없음…
    JS 함수형 프로그래밍을 위한 사전 지식 : 순수함수, 일급함수
    자바스크립트의 함수형 프로그래밍 1 : 순수 함수란?
    순수 함수란? (함수형 프로그래밍의 뿌리, 함수의 부수효과를 없앤다)

  • [JavaScript] try…catch를 이용한 에러 핸들링

    [JavaScript] try…catch를 이용한 에러 핸들링

    자바스크립트에서 에러가 발생하면 코드는 멈추게 되고, 콘솔에 에러가 출력된다. 하지만 try...catch 문법을 사용하면 스크립트가 죽는 것을 방지하고, 에러 상황을 잡아 예외처리를 할 수 있게 한다. 기본적인 형태는 두 블록으로 구성되며 예시 코드는 아래와 같다.

    기본 형태

    try {
    
      // 이 구간에서 에러가 발생하면 catch로 이동
    
    } catch (err) {
    
      // 에러 핸들링
    
    }
    
    • 먼저 try 블록의 코드가 실행된다.
    • try 블록 안에 에러가 없다면 catch 블록은 건너 뛴다.
    • try 블록 안에서 에러 코드를 만나면 try 블록의 실행이 중단되고 catch 블록의 코드가 실행된다.
    • err 객체에는 에러에 대한 정보가 있다.

    실제 작동 코드 살펴보자.

    try {
      console.log("아직 에러 없음");
    
      a; // 에러 시작
    
      console.log("이곳은 실행 안됨");
    
    } catch (err) {
    
      console.log(err); // a is not defined
    
      console.log("에러가 나도 이곳의 코드는 실행됨");
    
    }
    

    try 블록에서 에러가 나면 아래의 console.log()는 실행이 안되며 바로 catch 블록으로 넘어간다. err 객체가 콘솔창에 어떤 에러인지 표시해 주며, 에러가 발생해도 catch 블록의 코드는 계속 실행되는 것을 확인할 수 있다. 이 부분에 에러 예외 처리를 작성해 주면 된다.

    try...catch는 런타임 에러에만 작동한다

    try...catch는 실행이 가능한 코드에만 동작하며, 중괄호가 들어가는 등 자바스크립트 엔진이 해석할 수 없는 문법적 오류(SyntaxError)같은 경우는 작동하지 않는다.

    try {
        
        {}{ // SyntaxError
    
      console.log("자바스크립트 엔진은 이 코드를 이해할 수 없어 실행 자체가 안됨");
    
    } catch (err) {
    
      console.log("여기도 실행 안됨");
    
    }
    

    try...catch는 동기적으로 동작한다

    try...catchsetTimeout와 같이 비동기적으로 실행되는 코드의 에러는 잡아낼 수 없다.

    try {
        
      setTimeout(() => {
    
        a; // 에러가 발생하지만 catch가 잡아낼 수 없음
    
      }, 1000);
    
    } catch (err) {
    
      console.log("try 블록의 에러를 잡아낼 수 없음");
    
    }
    

    1초뒤 setTimeout의 익명함수가 실행되고 에러가 발생하지만 이미 자바스크립트 엔진은 try...catch 블록을 떠났기 때문에 오류를 잡아낼 수 없다. 비동기로 실행되는 코드의 에러를 잡으려면 반드시 해당 함수 안에서 try...catch 구문을 사용해야 한다.

    setTimeout(() => {
    
      try {
    
        a; // 에러가 발생하지만 catch가 잡아낼 수 있음
    
      } catch (err) {
    
        console.log("try 블록의 에러를 잡아냄");
    
      }
      
    }, 1000);
    

    에러 객체

    에러가 발생하면 자바스크립트는 에러 내용이 담긴 객체를 생성하고 catch 블록의 인수로 전달한다.

    try {
    
      a; // 에러 시작
    
    } catch (err) {
    
      console.log(err); // ReferenceError: a is not defined
      console.log(err.name); // ReferenceError
      console.log(err.message); // a is not defined
      
    }
    

    name 프로퍼티는 에러의 이름을 나타내고 message는 에러에 대한 상세 내용을 가지고 있다.

    직접 에러를 생성해 던지기

    throw 연산자

    throw 연산자는 예외를 던질 수 있으며, catch 블록에 전달된다.

    throw 연산자는 함수의 실행을 중단한다는 표현과 같다.

    try {
    
      throw "예외 처리를 던짐";
    
      console.log("여긴 실행 안됨");
    
    } catch (err) {
    
      console.log(err); // 예외 처리를 던짐
      
    }
    

    위 코드에서 throw는 예외를 던지고 있으며, throw 아래의 로직은 실행이 안된다. throw 연산자자와 에러 객체 생성자를 이용하여 예외 처리를 해보자.

    에러 객체 생성자

    const error = new Error("에러 발생");
    const syntaxError = new SyntaxError("문법 에러 발생");
    
    console.log(error); // Error: 에러 발생
    console.log(syntaxError); // SyntaxError: 문법 에러 발생
    

    자바스크립트는 Error, SyntaxError, ReferenceError, TypeError 등 표준 애러 객체 생성자를 지원하며, 이 생성자들을 이용해 에러 객체를 만들 수 있다.

    생성한 에러 던지기

    const person = {
      name: "foo",
      age: 20,
    };
    
    try {
    
      if (!person.gender) {
        throw new SyntaxError("성별이 없음");
      }
    
      console.log("이곳은 실행이 안됨"); // person.gender 값이 없기 때문에 실행이 안됨
    
    } catch (err) {
    
      console.log(err); // Error: 성별이 없음
      
    }
    

    위 코드의 try 블록에서는 성별 값이 있는지 체크하고 있는데, 체크 대상인 person 객체에는 이름과 나이만 있고 성별이 없다. 때문에 성별이 없을 때 직접 생성한 에러를 던지고 있고, catch 블록에서 에러를 받아 출력하고 있다.

    참고로 위 에러는 직접 생성한 에러이기 때문에 실제 SyntaxError 에러는 아니다.

    에러 다시 던지기

    try...catch는 애초에 try 블록에서 발생한 모든 에러를 잡는 목적으로 만들어졌다. 에러의 종류와 상관 없이 모든 에러를 잡는것은 디버깅에 어려움을 주기 때문에 예상치 못한 에러를 다시 던져서 에러의 종류에 따라 대응을 해줘야 한다.

    const person = {
      name: "foo",
      age: 20,
    };
    
    const getError = () => {
      try {
        if (!person.gender) {
          throw new SyntaxError("성별이 없음");
          // throw new ReferenceError("성별이 없음");
        }
      } catch (err) {
        if (err instanceof SyntaxError) {
          console.log("이 에러는 " + err); // SyntaxError일 경우
        } else {
          throw err; // SyntaxError가 아닐 경우 밖으로 다시 던짐
        }
      }
    };
    
    try {
      getError();
    } catch (err) {
      if (err instanceof ReferenceError) {
        console.log("이 에러는 " + err); // ReferenceError일 경우
      }
    }
    

    getError()가 실행되면서 SyntaxError를 잡아내고 있으며, 만약 SyntaxError 에러가 아닐 경우는 다시 에러를 함수 밖으로 던지며, 함수 외부에서 에러를 다시 잡고 있다. 함수 밖에서는 ReferenceError일 경우를 잡아내고 있다. SyntaxError일 경우는 함수 내부에서 에러를 잡고 ReferenceError일 경우는 함수 외부에서 잡는다고 보면 된다.

    finally

    finally절은 에러의 유무와 상관없이 마지막으로 사용되는 블록이며, 마지막 제어가 필요할 때 사용하면 된다.

    try {
        
      throw new Error("에러 발생");
    
    } catch (err) {
    
      console.log(err); // 에러 발생
    
    } finally {
    
      console.log("항상 실행"); // 항상 실행
    
    }
    

    References

    ‘try..catch’와 에러 핸들링
    에러 처리를 어떻게 하면 좋을까? – 1
    예외 ( throw,[try/catch/finally])

  • [JavaScript] Fetch API

    [JavaScript] Fetch API

    자바스크립트의 fetch 함수는 비동기 통신 API로써 서버에 네트워크 요청을 보내 새로운 정보를 받아올 수 있다. ES6부터 지원하며, 가독성이 매우 뛰어난 장점이 있다. 이곳을 클릭하면 {"message": "hello world"}라는 JSON 데이터 화면이 나온다. fetch API를 이용해 이 JSON을 가져와 보자.

    기본 형태

    fetch API의 기본 형태는 아래와 같다.

    fetch(url, [options])
      .then((res) => res.json())
      .then((res) => {
        // data를 응답 받은 후 로직
      });
    
    • url에는 접근하고자 경로를 넣으면 된다.
    • options에는 methodheader등을 지정하여 요청할 수 있다.
    • options에 아무값도 넘기지 않으면 요청은 GET 메서드로 진행된다.

    화살표 함수를 함수 선언식으로 변경하면 아래와 같다.

    fetch(url, [options])
      .then(function(res) {
        return res.json();
      })
      .then(function(res) {
        // data를 응답 받은 후 로직
      });
    

    위에서 언급한 주소를 입력하여 JSON 데이터를 잘 가져오는지 확인해 본다.

    요청 하기

    fetch('https://recordboy.github.io/ui/dummy/data.json')
      .then(function(res) {
    
        // Response Object
        console.log(res);
    
        // 응답값 JSON 형태로 얻기
        return res.json();
      })
      .then(function(res) {
    
        // 리턴 받은 JSON
        console.log(res);
      });
    
    • 응답값은 첫번째 then에 지정된 함수의 res에 담겨지며, 이 값은 http 응답값을 가지고 있는 Response Object이다.
    • 첫번째 then의 응답값을 JSON 형태로 얻기 위해 Response Objectjson() 함수를 호출하여 값을 리턴한다.
    • 두번째 then에서 응답 받은 JSON을 확인할 수 있다.

    References

    fetch
    fetch() 함수 사용법
    Javascript에서의 비동기 통신

  • [JavaScript] 비동기 처리를 위한 프로미스

    [JavaScript] 비동기 처리를 위한 프로미스

    정의

    자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 콜백 패턴은 가독성이 나쁘고, 비동기 처리 중 발생한 에러의 예외처리가 곤란하며, 여러개의 비동기 처리 로직을 한꺼번에 처리하는 것도 한계가 있다. ES6에서 비동기 처리를 위한 또 다른 패턴으로 프로미스(Promise)를 도입하였다. 프로미스는 비동기적으로 요청한 결과(성공/실패)를 나타내는 객체로서, 콜백 패턴이 가진 단점을 보완하며, 비동기 처리 시점을 명확하게 표현한다.

    기존의 콜백 함수 패턴

    기존의 비동기 처리 방식은 콜백 패턴을 이용하는 방법이였으며 코드는 아래와 같다.

    function findUser(id, callback) {
      setTimeout(function () {
        const user = {
          id: id,
          name: 'my name is ' + id
        };
        callback(user);
      }, 1000);
    }
    
    findUser('foo', function (user) {
      console.log(user); // { id: 'foo', name: 'my name is foo' }
    });
    

    user객체의 정보를 받아오는 구조다. getUser() 함수를 호출할 때 두번째 인자로 콜백 함수가 할당된다. setTimeout()함수로 인하여 1초 뒤 user객체의 정보를 담는 로직이 실행되며 결과물이 두번째 인자로 들어간 콜백 함수의 인자에 할당되어 콜백 함수가 호출된다.

    프로미스 패턴

    상단의 비동기 처리 방식을 프로미스를 이용하면 아래와 같이 작성할 수 있다.

    function findUser(id) {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          const user = {
            id: id,
            name: 'my name is ' + id,
          };
          resolve(user);
        }, 1000);
      });
    }
    
    findUser('foo').then(function (user) {
      console.log(user); // { id: 'foo', name: 'my name is foo' }
    });
    

    위 코드는 콜백 함수를 인자로 넘기는 대신에 프로미스 객체를 생성하여 리턴을 하고, 리턴받은 프로미스 객체의 then()메서드를 호출하여 실행할 로직을 인자값으로 넣어줬다. 실행할 로직은 resolve 메서드에 할당되어 user객체의 결과값을 콘솔창에 출력하고 있다. resolve()then()가 생소하다. 아래에서 천천히 알아보도록 하겠다.

    프로미스의 3가지 상태

    프로미스는 3가지의 상태(states)를 가지며, 여기서 상태란 프로미스의 처리 과정을 의미한다. new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 가진다.

    • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
    • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
    • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

    Pending(대기)

    아래처럼 new Promise()생성자를 호출하면 대기 상태가 된다. new Promise() 생성자를 호출할 때 인자로 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject이다.

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

    실제로는 변수에 할당하기 보단 어떤 함수의 리턴값으로 많이 사용되며, 생성자의 인자로 화살표 함수를 많이 사용한다.

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

    Fulfilled(이행)

    넘겨 받은 인자에서 resolve를 함수로 실행하면 이행(완료) 상태가 된다.

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

    이행 상태에서 아래와 같이 then()메서드를 이용하면 결과값을 받을 수 있다.

    function returnPromise() {
      return new Promise((resolve, reject) => {
        const data = 'my name is foo';
        resolve(data);
      });
    }
    
    returnPromise().then((data) => {
      console.log(data); // my name is foo
    });
    

    Rejected(실패)

    넘겨 받은 인자에서 reject를 함수로 실행하면 실패 상태가 된다. 실패 상태가 되면 실패 처리의 결과값을 catch()메서드를 통해 받을 수 있다.

    function returnPromise() {
      return new Promise((resolve, reject) => {
        reject(new Error('Request is failed'));
      });
    }
    
    returnPromise()
      .then()
      .catch((err) => {
        console.log(err); // Error: Request is failed
      });
    

    사용 방법

    기본 코드

    아래는 응답값에 따라 결과를 출력하는 예제이다.

    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
    
          const data = 'my-data';  // data 값이 있다면 data 값을 출력 
          // const data = ''; // data 값이 없다면 에러를 출력
    
          if (data) {
            resolve(data);
          }
          reject(new Error('Request is failed'));
        }, 1000);
      });
    }
    
    getData()
      .then((data) => {
        console.log(data); // my-data
      })
      .catch((err) => {
        console.log(err); // Error: Request is failed
      });
    

    setTimeout()함수로 1초 뒤 data값을 받아오며, data값이 있을 경우 then()메서드를 호출하고, data값이 없을 경우 catch() 메서드를 호출한다.

    프로미스 연결(Promise Chaining)

    프로미스의 특징으로 여러개의 프로미스를 연결하여 사용할 수 있다. then()메서드를 호출하면 새로운 프로미스 객체가 반환된다. 아래처럼 각 프로미스를 연결하여 사용할 수 있다.

    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout((data) => {
    
          const data = 10; // data 값이 10일 경우
    
          if (data) {
            resolve(data);
          }
          reject(new Error('Request is failed'));
        }, 1000);
      });
    }
    
    getData()
      .then((data) => {
        console.log(data); // 10
        return data + 10;
      })
      .then((data) => {
        console.log(data); // 20
        return data + 10;
      })
      .then((data) => {
        console.log(data); // 30
      })
      .catch((err) => {
        console.log(err); // Error: Request is failed
      });
    

    setTimeout()함수에서 data값이 10으로 왔다고 가정해 본다. 첫번째 then()메서드의 data에 10을 더하여 리턴하고 두번째 then()에 더한 data를 인자로 넣어 호출한다. 이와 같은 방법으로 then()함수를 연결하여 사용할 수 있다.

    References

    [자바스크립트] 비동기 처리 2부 – Promise
    자바스크립트 Promise 쉽게 이해하기
    JavaScript 비동기 처리를 위한 promise 이해하기
    Promise
    프로미스

  • [JavaScript] 랜덤 배열(Shuffle)

    [JavaScript] 랜덤 배열(Shuffle)

    아래는 배열을 무작위로 섞는 알고리즘이며, arr배열을 선언한 뒤 각 요소를 랜덤으로 추출하여 newArr라는 새로운 배열에 할당하도록 작성되었다.

    let arr = [1, 2, 3, 4, 5],
      newArr = [],
      len = arr.length,
      ranIdx = 0,
      ran = 0,
      target = 0;
    
    console.log(arr);
    // [ 1, 2, 3, 4, 5 ]
    
    for (let i = 0; i < len; i++) {
      ranIdx = Math.floor(Math.random() * arr.length);
      ran = arr[ranIdx];
      if (arr.indexOf(ran) !== -1) {
        target = arr.indexOf(ran);
        arr.splice(target, 1);
        newArr.push(ran);
      }
    }
    console.log(newArr);
    // 무작위로 배열 출력
    

    구글링을 하다 더욱 간결한 코드를 발견하였다. 코드는 아래와 같다.

    function shuffle(a) {
      let j, x, i;
      for (let i = a.length; i; i -= 1) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        a[i - 1] = a[j];
        a[j] = x;
      }
      console.log(a);
      // 무작위로 배열 출력
    }
    shuffle([1, 2, 3, 4, 5]);
    

    shuffle()에 배열을 인자로 넘겨 호출한다. 함수안 반복문의 인덱스 값은 내림차순으로 할당된다. Math.random()로 배열 요소의 난수를 구한 뒤 x에 배열의 마지막 요소를 할당하고, 마지막 요소 자리에 난수를 인덱스로 가지는 요소를 대입한다. 난수를 인덱스 값으로 가지는 요소 자리에는 아까 할당해둔 x값을 대입한다. 이 로직을 반복하여 랜덤 배열을 얻게 된다.