본문 바로가기
Programing/Javascript

JavaScript Promises 무엇인가?

by 멍멍돌이야 2022. 7. 4.
반응형

Why JavaScript promises(promises 무엇인가?)

다음 예제는 사용자 객체 목록을 반환하는 getUsers() 함수를 정의합니다.

function getUsers() {
  return [
    { username: 'john', email: 'john@test.com' },
    { username: 'jane', email: 'jane@test.com' },
  ];
}

각 사용자 개체에는 username과 email의 두 가지 속성이 있습니다.

getUsers() 함수에서 반환된 사용자 목록에서 사용자 이름으로 사용자를 찾으려면 다음과 같이 findUser() 함수를 사용할 수 있습니다.

function findUser(username) {
  const users = getUsers();
  const user = users.find((user) => user.username === username);
  return user;
}

findUser() 함수에서:

  1. getUsers() 함수를 호출하여 사용자 배열을 가져옵니다.
  2. Array 객체의 find() 메서드를 사용하여 특정 사용자 이름을 가진 사용자를 찾습니다.
  3. 일치하는 사용자를 반환합니다.

다음은 사용자 이름이 'john'인 사용자를 찾기 위한 전체 코드를 보여줍니다.

function getUsers() {
  return [
    { username: 'john', email: 'john@test.com' },
    { username: 'jane', email: 'jane@test.com' },
  ];
}

function findUser(username) {
  const users = getUsers(); 
  const user = users.find((user) => user.username === username);
  return user;
}

console.log(findUser('john'));

Output:

{ username: 'john', email: 'john@test.com' }

findUser() 함수의 코드는 동기 및 차단입니다. findUser() 함수는 getUsers() 함수를 실행하여 사용자 배열을 가져오고 users 배열에서 find() 메서드를 호출하여 특정 사용자 이름을 가진 사용자를 검색하고 일치하는 사용자를 반환합니다.

실제로 getUsers() 함수는 데이터베이스에 액세스하거나 API를 호출하여 사용자 목록을 가져올 수 있습니다. 따라서 getUsers() 함수는 지연됩니다.

지연을 시뮬레이션하려면 setTimeout() 함수를 사용할 수 있습니다. 예를 들어 다음과 같습니다.

function getUsers() {
  let users = [];

  // delay 1 second (1000ms)
  setTimeout(() => {
    users = [
      { username: 'john', email: 'john@test.com' },
      { username: 'jane', email: 'jane@test.com' },
    ];
  }, 1000);

  return users;
}

작동 원리:

  1. 배열 사용자를 정의하고 빈 배열로 값을 초기화합니다.
  2. 사용자 배열을 setTimeout() 함수의 콜백 내부에 있는 users 변수에 할당합니다.
  3. 사용자 배열을 반환합니다.

getUsers()는 제대로 작동하지 않으며 항상 빈 배열을 반환합니다. 따라서 findUser() 함수는 예상대로 작동하지 않습니다.

function getUsers() {
  let users = [];
  setTimeout(() => {
    users = [
      { username: 'john', email: 'john@test.com' },
      { username: 'jane', email: 'jane@test.com' },
    ];
  }, 1000);
  return users;
}

function findUser(username) {
  const users = getUsers(); // A
  const user = users.find((user) => user.username === username); // B
  return user;
}

console.log(findUser('john'));

Output:

undefined

getUsers()가 빈 배열을 반환하기 때문에 users 배열은 비어 있습니다(라인 A). 

사용자 배열에서 find() 메서드를 호출하면 이 메서드는 정의되지 않은 값을 반환합니다.(라인 B)

문제는 1초 후에 getUsers() 함수에서 반환된 사용자에 액세스하는 방법입니다. 한 가지 고전적인 접근 방식은 콜백을 사용하는 것입니다.

 

Using callbacks to deal with an asynchronous operation

(콜백을 사용하여 비동기 작업 처리)

 

다음 예제에서는 getUsers() 및 findUser() 함수에 콜백 인수를 추가합니다.

function getUsers(callback) {
  setTimeout(() => {
    callback([
      { username: 'john', email: 'john@test.com' },
      { username: 'jane', email: 'jane@test.com' },
    ]);
  }, 1000);
}

function findUser(username, callback) {
  getUsers((users) => {
    const user = users.find((user) => user.username === username);
    callback(user);
  });
}

findUser('john', console.log);

Output:

{ username: 'john', email: 'john@test.com' }

이 예에서 getUsers() 함수는 콜백 함수를 인수로 받아들이고 setTimeout() 함수 내부의 사용자 배열을 사용하여 호출합니다. 또한 findUser() 함수는 일치하는 사용자를 처리하는 콜백 함수를 받습니다.

콜백 접근 방식은 매우 잘 작동합니다. 그러나 코드를 따르기 더 어렵게 만듭니다. 또한 콜백 인수가 있는 함수에 복잡성을 추가합니다.

함수의 수가 늘어나면 콜백 지옥 문제가 발생할 수 있습니다. 이를 해결하기 위해 JavaScript는 Promise의 개념을 제시합니다.

 

Understanding JavaScript Promises(Promises 이해하기)

정의에 따르면 Promise는 비동기 작업의 결과를 캡슐화하는 개체입니다.

Promise 객체의 상태는 다음 중 하나일 수 있습니다.

  • Pending(보류 중)
  • Fulfilled with a value(값으로 채워진)
  • Rejected for a reason(어떤이유로 거부됨)

처음에는 비동기 작업이 진행 중임을 나타내는 약속 상태가 보류 중입니다. 비동기 작업의 결과에 따라 상태가 이행됨 또는 거부됨으로 변경됩니다.

fulfilled 상태는 비동기 작업이 성공적으로 완료되었음을 나타냅니다.

거부된 상태는 비동기 작업이 실패했음을 나타냅니다.

 

Creating a promise(만들기)

Promise 객체를 생성하려면 Promise() 생성자를 사용합니다.

const promise = new Promise((resolve, reject) => {
  // contain an operation
  // ...

  // return the state
  if (success) {
    resolve(value);
  } else {
    reject(error);
  }
});

Promise 생성자는 일반적으로 비동기 작업을 수행하는 콜백 함수를 허용합니다. 이 기능을 종종 실행자라고 합니다.

 

차례로, 실행자는 이름이 resolve 및 reject인 두 개의 콜백 함수를 수락합니다.

실행기로 전달된 콜백 함수는 규칙에 따라 확인 및 거부됩니다.

비동기 작업이 성공적으로 완료되면 실행자는 resolve() 함수를 호출하여 약속의 상태를 값으로 보류에서 이행으로 변경합니다.

오류가 발생하면 실행자는 reject() 함수를 호출하여 오류 이유와 함께 약속의 상태를 보류에서 거부로 변경합니다.

Promise가 fulfilled 또는 거부된 상태에 도달하면 해당 상태에 머물며 다른 상태로 이동할 수 없습니다.

즉, Promise는 이행 상태에서 거부 상태로 또는 그 반대로 갈 수 없습니다. 또한 이행 또는 거부 상태에서 보류 상태로 돌아갈 수 없습니다.

새 Promise 개체가 생성되면 해당 상태는 보류 중입니다. 약속이 이행 또는 거부된 상태에 도달하면 해결됩니다.

실제로 Promise 객체를 생성하는 경우는 거의 없습니다. 대신 라이브러리에서 제공하는 약속을 사용합니다.

 

Consuming a Promise: then, catch, finally(사용법)

1) The then() method

프라미스가 이행되었을 때 그 값을 얻으려면 프라미스 객체의 then() 메서드를 호출합니다. 다음은 then() 메서드의 구문을 보여줍니다.

promise.then(onFulfilled,onRejected);

then() 메서드는 onFulfilled 및 onRejected의 두 가지 콜백 함수를 허용합니다.

then() 메서드는 약속이 이행되면 onFulfilled()를 값으로 호출하고, 약속이 거부되면 오류와 함께 onRejected()를 호출합니다.

 

onFulfilled 및 onRejected 인수는 모두 선택 사항입니다.

다음 예제는 getUsers() 함수에서 반환된 Promise 객체의 then() 메서드를 사용하는 방법을 보여줍니다.

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { username: 'john', email: 'john@test.com' },
        { username: 'jane', email: 'jane@test.com' },
      ]);
    }, 1000);
  });
}

function onFulfilled(users) {
  console.log(users);
}

const promise = getUsers();
promise.then(onFulfilled);

Output:

[
  { username: 'john', email: 'john@test.com' },
  { username: 'jane', email: 'jane@test.com' }
]

이 예에서:

  1. promise가 이행될 때 호출될 onFulfilled() 함수를 정의합니다.
  2. getUsers() 함수를 호출하여 promise 객체를 가져옵니다.
  3. Promise 객체의 then() 메서드를 호출하여 콘솔에 사용자 목록을 출력합니다.

코드를 더 간결하게 만들기 위해 다음과 같이 then() 메서드의 인수로 화살표 함수를 사용할 수 있습니다.

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { username: 'john', email: 'john@test.com' },
        { username: 'jane', email: 'jane@test.com' },
      ]);
    }, 1000);
  });
}

const promise = getUsers();

promise.then((users) => {
  console.log(users);
});

getUsers() 함수가 promise 객체를 반환하기 때문에 다음과 같이 then() 메서드를 사용하여 함수 호출을 연결할 수 있습니다.

// getUsers() function
//...

getUsers().then((users) => {
  console.log(users);
});

이 예에서 getUsers() 함수는 항상 성공합니다. 오류를 시뮬레이션하기 위해 다음과 같은 성공 플래그를 사용할 수 있습니다.

let success = true;

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve([
          { username: 'john', email: 'john@test.com' },
          { username: 'jane', email: 'jane@test.com' },
        ]);
      } else {
        reject('Failed to the user list');
      }
    }, 1000);
  });
}

function onFulfilled(users) {
  console.log(users);
}
function onRejected(error) {
  console.log(error);
}

const promise = getUsers();
promise.then(onFulfilled, onRejected);

작동 원리:

  1. 성공 변수를 정의하고 값을 true로 초기화합니다. 성공이 true이면 getUsers() 함수의 약속이 사용자 목록으로 이행됩니다. 그렇지 않으면 오류 메시지와 함께 거부됩니다.
  2. onFulfilled 및 onRejected 함수를 정의합니다.
  3. getUsers() 함수에서 promise를 가져오고 onFulfilled 및 onRejected 함수로 then() 메서드를 호출합니다.

다음은 화살표 함수를 then() 메서드의 인수로 사용하는 방법을 보여줍니다.

// getUsers() function
// ...

const promise = getUsers();
promise.then(
  (users) => console.log,
  (error) => console.log
);

 

2) The catch() method

Promise 상태가 거부된 경우에만 오류가 발생하도록 하려면 Promise 객체의 catch() 메서드를 사용할 수 있습니다.

promise.catch(onRejected);

내부적으로 catch() 메서드는 then(undefined, onRejected) 메서드를 호출합니다.

다음 예제에서는 오류 시나리오를 시뮬레이션하기 위해 성공 플래그를 false로 변경합니다.

let success = false;

function getUsers() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve([
          { username: 'john', email: 'john@test.com' },
          { username: 'jane', email: 'jane@test.com' },
        ]);
      } else {
        reject('Failed to the user list');
      }
    }, 1000);
  });
}

const promise = getUsers();

promise.catch((error) => {
  console.log(error);
});

 

3) The finally() method

때로는 약속이 이행되든 거부되든 동일한 코드를 실행하고 싶을 때가 있습니다. 예를 들어:

const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
    render();
  })
  .catch((error) => {
    console.log(error);
    render();
  });

보시다시피, render() 함수 호출은 then() 및 catch() 메서드 모두에서 중복됩니다.

이 중복을 제거하고 약속이 이행되거나 거부되었는지 여부에 관계없이 render()를 실행하려면 다음과 같이 finally() 메서드를 사용합니다.

const render = () => {
  //...
};

getUsers()
  .then((users) => {
    console.log(users);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    render();
  });

 

A practical JavaScript Promise example(실용적인 Promise 예제)

다음 예제에서는 서버에서 JSON 파일을 로드하고 해당 내용을 웹 페이지에 표시하는 방법을 보여줍니다.

 

다음 JSON 파일이 있다고 가정합니다.

https://www.javascripttutorial.net/sample/promise/api.json

다음 내용으로:

{
    "message": "JavaScript Promise Demo"
}

다음은 버튼이 포함된 HTML 페이지를 보여줍니다. 버튼을 클릭하면 페이지가 JSON 파일에서 데이터를 로드하고 다음 메시지를 표시합니다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JavaScript Promise Demo</title>
    <link href="css/style.css" rel="stylesheet">
</head>
<body>
    <div id="container">
        <div id="message"></div>
        <button id="btnGet">Get Message</button>
    </div>
    <script src="js/promise-demo.js">
    </script>
</body>
</html>

다음은 promise-demo.js 파일을 보여줍니다.

function load(url) {
  return new Promise(function (resolve, reject) {
    const request = new XMLHttpRequest();
    request.onreadystatechange = function () {
      if (this.readyState === 4 && this.status == 200) {
        resolve(this.response);
      } else {
        reject(this.status);
      }
    };
    request.open('GET', url, true);
    request.send();
  });
}

const url = 'https://www.javascripttutorial.net/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
  load(URL)
    .then((response) => {
      const result = JSON.parse(response);
      msg.innerHTML = result.message;
    })
    .catch((error) => {
      msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
    });
});

작동 원리:
1. XMLHttpRequest 객체를 사용하여 서버에서 JSON 파일을 로드하는 load() 함수를 정의합니다.

function load(url) {
  return new Promise(function (resolve, reject) {
    const request = new XMLHttpRequest();
    request.onreadystatechange = function () {
      if (this.readyState === 4 && this.status == 200) {
        resolve(this.response);
      } else {
        reject(this.status);
      }
    };
    request.open('GET', url, true);
    request.send();
  });
}

실행기에서 HTTP 상태 코드가 200이면 Response와 함께 resolve() 함수를 호출합니다. 그렇지 않으면 HTTP 상태 코드로 reject() 함수를 호출합니다.

2. 버튼 클릭 이벤트 리스너를 등록하고 promise 객체의 then() 메서드를 호출합니다. 로드가 성공하면 서버에서 반환된 메시지가 표시됩니다. 그렇지 않으면 HTTP 상태 코드와 함께 오류 메시지가 표시됩니다.

const url = 'https://www.javascripttutorial.net/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
  load(URL)
    .then((response) => {
      const result = JSON.parse(response);
      msg.innerHTML = result.message;
    })
    .catch((error) => {
      msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
    });
});

 

요약

  • Promise는 비동기 작업의 결과를 캡슐화하는 개체입니다.
  • 약속은 보류 상태에서 시작하여 이행 상태 또는 거부 상태에서 끝납니다.
  • then() 메서드를 사용하여 약속이 이행될 때 콜백이 실행되도록 예약하고 catch() 메서드를 사용하여 약속이 거부될 때 호출되도록 콜백을 예약합니다.
  • 약속이 이행되거나 거부되었는지 여부에 관계없이 실행하려는 코드를 finally() 메서드에 넣습니다.

 

 

 

 

 

 

 

Reference: https://www.javascripttutorial.net/es6/javascript-promises/

 

728x90
반응형

댓글