[Node.js] 비동기 / Promise 동작원리 / SPRINT-비동기 💯️

jungeundelilahLEE·2020년 12월 17일
2

Node.js

목록 보기
1/27

goal 💯️

  • 어떤 경우에 중첩된 callback이 발생하는지 이해할 수 있다.
  • 중첩된 callback의 단점, Promise 의 장점을 이해할 수 있다.
  • Promise 사용 패턴과 언어적인 특징들을 이해할 수 있다.
  • resolve, reject의 의미와, then, catch와의 관계를 이해할 수 있다.
  • Promise 에서 인자를 넘기는 방법에 대해 이해할 수 있다.
  • Promise의 세가지 상태를 이해할 수 있다.
  • Promise.all 의 사용법을 이해할 수 있다.
  • async/await keyword에 대해 이해하고, 작동 원리를 이해할 수 있다.
  • node.js의 fs 모듈의 사용법을 이해한다.

promise💯️

  • 일종의 하나의 class
new Promise()
resolve()  	// => go to next action
reject() 	//=> handle error

예제

// 콜백헬
const printString = (string, callback) => {
  setTimeout(() => {
    console.log(string)
    callback()
  }, Math.floor(Math.random() * 100) + 1);
}
const printAll = () => {
  printString ("A", () => {
    printString ("B", () => {
      printString ("C", () => {})
    })
  })
}
printAll()  // ABC
////////////////////////////////////////////////

//프로미스를 활용
const printString = (string) => {
  return new Promise ((resolve, reject) => {
    setTimeout(() => {
      console.log(string)
      resolve()
    }, Math.floor(Math.random() * 100) + 1);
  })
}
const printAll = () => {
  printString ("A")
  .then(() => {
    return printString("B")
  })
  .then(() => {
    return printString("C")
  })
}
printAll()

// 콜백을 인자로 받지 않고, 새로운 인스턴스인 Promise


promise 💯️

  • 프로미스는 주로 서버에서 받아온 데이터를 화면에 표시하기 위해 사용한다.

1. promise 생성방법

  • promise는 Promise생성자함수를 통해서 인스턴스화 된다.
  • Promise생성자함수는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데, 이 콜백함수는resolvereject함수를 인자로 전달받는다.
// promise 객체생성 
const promise = new Promise((resolve, reject) => {
  // 여기서 비동기 작업을 수행한다.
  if (/* 비동기 작업 수행 성공 */) {
    resolve('success');
  }
  else { /* 비동기 작업 수행 실패 */
    reject('failure');
  }
});

2. promise의 상태(state)

상태의미
pending
resolve or reject 함수가 아직 호출되지 않은 상태
비동기 처리가 아직 수행되지 않은 상태 (성공 or 실패 둘다 X)
fulfilled
resolve 함수가 호출된 상태
비동기 처리가 수행되어 결과값을 반환해준 상태 (성공!)
rejected
reject 함수가 호출된 상태
비동기 처리가 수행되었지만 실패하거나 오류가 발생한 상태 (실패ㅜ)
settled
resolve or reject 함수가 호출된 상태
비동기 처리가 수행된 상태 (성공 or 실패)

3. promise의 후속 처리 메소드 - then / catch

  • then
    • 두 개의 콜백 함수를 인자로 전달받는다.
    • 첫번째 콜백함수는 성공할 때 호출되며, 두번째 콜백함수는 실패할 때 호출된다.
    • then메소드는 Promise를 반환한다.
  • catch
    • 예외가 발생할 때 호출된다.
    • catch메소드는 Promise를 반환한다.

4. promise의 에러 처리

  • then 메서드의 두번째 콜백 함수로 처리할 수 있다.
  • Promise객체의 후속 처리 메서드인 catch를 사용해서 처리할 수 있다. 👈️권장!!

5. promise의 정적 메소드

  • resolve / reject / all / race 가 있다.

6. promise 예제

const 프로미스  = true;
const promise = new Promise( (resolve,reject) => {
    if (프로미스) {
        resolve("성공!")
    } else {
        reject("실패ㅜ")
    }
})
promise
.then((data) => {
    console.log(data)
})
.catch((err) => {
    console.error(err)
})
  • new Promise로 프로미스를 생성할 수 있다.
  • 프로미스에는 resolvereject 를 매개변수로 갖는 콜백 함수를 넣어줄 수 있다.
  • 프로미스 내부에서 resolve가 호출되면, then의 내용이 실행되고,
  • reject가 호출되면, catch의 내용이 실행된다.
  • then이나 catch에서 다시 다른 then, catch를 붙일 수도 있다.
  • 위의 기능에 덧붙여 프로미스가 "콜백의 기능"을 대신할 수 있다. (그럼에도 불구하고 콜백을 쓸 수 밖에 없다..)
  • then의 메서드들은 "순차적으로 실행"된다. (가독성을 높인다)

Promise.all 메소드 💯️

문법

  • let promise = Promise.all([...promises...])
  • 인자로는 요소 전체가 프로미스인 순회가능한 객체를 받음 (대다수의 경우, 이터러블 객체는 배열이다>_<)
  • [...promises...]이들이 모두 처리되면, 새로운 프로미스가 처리되며, 새로운 결과값은 배열에 담김
  • 이말인 즉슨, 모두 처리되면 다같이 then으로 넘어갈 수 있지만, 하나라도 에러가나거나 처리가 안되면, catch로 넘어간다. (인자가 && 연산자처럼 묶여있다고 생각하면 된다)

활용

  • 여러 개의 프로미스를 동시에 실행시킬 때, 이 결과를 집계(다른단어가 없을까..)할 때 유용

예제

  • result배열의 요소 순서는 Promise.all에 전달되는 프로미스의 순서와 같다. 👇️
let result = Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
])
.then (console.log(result))
// 전체가 처리되면 1,2,3이 반환된다.
  • 작업해야 할 데이터가 담긴 배열을 프로미스 배열로 mapping한 후, 결과 배열을 Promise.all로 감싸는 방법은 자주 사용됨 👇️
let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://api.github.com/users/jeresig'
];

// fetch를 사용해 url을 promise로 mapping
let requests = urls.map(url => fetch(url));

// Promise.all은 모든 작업이 마칠 때까지 기다린다.
Promise.all(requests)
  .then(responses => responses.forEach(
    response => alert(`${response.url}: ${response.status}`)
  ));
// 차례대로 
// https://api.github.com/users/iliakan: 200,
// https://api.github.com/users/remy: 200,
// https://api.github.com/users/jeresig: 200

SPRINT - 비동기 💯️

Promise를 then으로 연결

const path = require('path');	 // path 모듈은 운영체제별로 경로 구분자가 달라 생기는 문제를 쉽게 해결하기 위해 등장
const { getDataFromFilePromise } = require('./02_promiseConstructor');	// 02에서 해당 함수를 가져옴

const user1Path = path.join(__dirname, 'files/user1.json'); 
const user2Path = path.join(__dirname, 'files/user2.json');
console.log(user1Path) 
// /home/jungeundelilahlee/dev/codestates/PAIR/im-sprint-async-and-promise/part-2/files/user1.json

// 노드는 해당키워드(__dirname)로 경로에 대한 정보를 제공함
//! __filename : file명을 포함한 절대 경로
//! __dirname : file명을 제외한 절대경로
//! path.join : 위에처럼 나옴 (콘솔로 확인가능)

const readAllUsersChaining = () => {

  return getDataFromFilePromise(user1Path)
  .then((user1) => {
    return getDataFromFilePromise(user2Path)
    .then((user2) =>{
      return `[${user1}, ${user2}]`
    })    
  })
  .then((str) => JSON.parse(str))
  
}
// readAllUsersChaining();
module.exports = {
  readAllUsersChaining
}

Promise.all을 이용

const readAllUsers = () => {

  //! 실패한 방법
  //? let requests = [`${user1Path}, ${user2Path}`]
  //? console.log(requests) // 못가져옴...
  //? console.log(user1Path) // 이렇게하면 그냥 파일 path만 가져올 수 있음
  //? console.log(getDataFromFilePromise(user1Path)) // pending 상태에 머무름
  
  //? return Promise.all(requests)
  //? .then((result) => {
  //?   let dd = []
  //?   for (let i = 0; i < result.length; i++) {
  //?     dd.push(result[i])
  //?   }
  //?   return dd
  //? })
  //? .then((str) => JSON.parse(str))

  return Promise.all([getDataFromFilePromise(user1Path), getDataFromFilePromise(user2Path)])
  .then(([u1, u2]) => {
    return `[${u1}, ${u2}]`
  })
  .then((str) => JSON.parse(str))
}

async/await 키워드를 이용

const readAllUsersAsyncAwait = async () => {
  
  // async
  //! function 키워드 앞에 async 키워드 위치
  //! function 앞에 async를 붙이면 해당 함수는 항상 프라미스를 반환
  //! 프라미스가 아닌 값을 반환하더라도 이행 상태의 프라미스(resolved promise)로 값을 감싸 이행된 프라미스가 반환되도록함
  //! async가 붙은 함수는 반드시 프라미스를 반환하고, 프라미스가 아닌 것은 프라미스로 감싸 반환함
  
  // await
  //! async 함수 안에서만 동작
  //! 자바스크립트는 await 키워드를 만나면 프라미스가 처리(settled)될 때까지 기다림. 결과는 그 이후 반환됨
  //! await는 내가 순서를 제어할 수 있음

  const user1 = await getDataFromFilePromise(user1Path);
  const user2 = await getDataFromFilePromise(user2Path);
  const str = `[${user1}, ${user2}]`;
  return JSON.parse(str);
}
profile
delilah's journey

0개의 댓글