JS 문법 - 비동기

KODYwiththeK·2023년 1월 1일
0

JavaScript

목록 보기
28/32

JS 문법 - 비동기

Class: 제로베이스
Created: December 27, 2022 7:10 AM
Type: Javascript
강의 명: 초심자도 빈틈없이 학습하는 자바스크립트

비동기

동기(동시에 발생함)의 반의어로, 동시에 발생하지 않음을 뜻함. → 순차적으로 발생

비동기로 코드를 짜야하는 이유는, a 작업이 수행된 후에 얻게된 데이터를 이용해서 b 작업을 수행해야 하기 때문

동기 코드

  • 작업 A를 수행함과 동시에 작업 B 가 실행됨
  • B의 작업은 데이터 a가 필요한 작업임
  • 아직 작업A가 진행중이므로 데이터 a가 존재하지 않음(에러)

비동기 코드

  • 작업 A가 실행된 후, 데이터 a를 가지고 작업 B 정상적으로 수행됨.

콜백 함수

  • 다른 함수의 인자로 넘겨져서 호출되는 함수
  • 비동기 코드로서 콜백함수를 많이 사용했지만, 작업이 5개만 되어도, 5개의 함수를 매개변수로 계속 넘겨줘야하고, 이런 코드 작성은 코드 파악을 어렵게한다.
const A = (callback) => {
  const a = 'A 함수 실행 후 생기는 데이터';
  callback(a);
}; 

const B = (a) => {
  console.log(`${a}를 이용하는 B`)
};

A(B); // 출력 : A 함수 실행 후 생기는 데이터를 이용하는 B
// A 함수가 실행되며 데이터 a가 생성되고, 
// a 데이터를 인자로 받는 B 함수가 순차적으로 실행

프라미스(Promise)

구독이라는 방법을 이용하는 비동기 구현 방법.

콜백 함수 대신 유용하게 쓸 수 있는 오브젝트.

state : pending → fulfulled or rejected

Producer vs Consumer

  1. producer

    새로운 promise 가 만들어질 때는, 전달한 executor라는 함수가 자동으로 실행된다.

new Promise 생성자 함수를 통해 생성 가능. 매개변수로 함수를 받는다.

resolve와 reject 함수가 그 매개변수.

  • 성공시 resolve 호출
  • 실패시 reject 호출

then 메서드 - 성공시 넘어오는 값을 result로 코드 구현

catch 메서드 - 실패시 넘어오는 에러 error 로 코드 구현

하지만, 프라미스 역시 then 절이 계속 이어지는 식으로 체이닝이 발생하기 때문에 복잡해질수록 코드 파악 어렵.

예제1)

const A2 = () => new Promise((resolve, reject) => {
  const a = 'A 함수 실행 후 생기는 데이터';
  resolve(a);
})

const B2 = (a) => {
  console.log(`${a}를 이용하는 B`);
};

A2()
.then((a) => {
  console.log('A2 실행 성공!');
  B2(a);
})
.catch((error) => {
  console.log(error.message);
})
  1. A2 함수 실행했을 때, A2 함수는 프라미스 객체를 반환받기 때문에, a라는 데이터가 정의되고,
  2. resolve(a)라는 함수가 실행되어서, a 라는 값을 가진 프라미스 객체가 A2 변수에 할당되고
  3. 프라미스 객체의 then 메서드 사용하면 메서드 내부의 함수로 정의된 매개변수에 그 성공한 값 들어감
  4. a 값이 B2에 매개변수로 들어가게 되고, B2가 실행됨

예제2)

const getHen = () => 
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 500);
  });

const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${hen} => 🥚`), 500);
  });

const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 500);
  });

getHen()
  .then((hen) => getEgg(hen))
	//.then(getEgg) -> then으로 받은 value를 바로 넘겨줄 때는 이렇게 작성 가능
  .then((egg) => cook(egg))
  .then((meal) => console.log(meal));
  // 1.5초 후에 🐓 => 🥚 => 🍳 출력

예제3) 에러 핸들링

const getHen = () => 
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 500);
  });
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 500);
  });
const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 500);
  });

getHen()
  .then((hen) => getEgg(hen))
  .catch(error => {
    return '👀';
  })
  .then((egg) => cook(egg))
  .then((meal) => console.log(meal))
  .catch(console.log);
  // 1.5초 후에 👀 => 🍳 출력

예제4) 로그인 페이지 만들기 예제

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if(
          (id === 'james' && password ==='asdf') || 
          (id === 'kody' && password === 'asdf') 
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 1000);
    });
  }

  getRoles(id) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if(id === 'james') {
          resolve({ name: 'james', role: 'admin' })
        } else {
          reject(new Error('no access'))
        }
      }, 1000)
    });
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password')
userStorage
  .loginUser(id, password)
  // userStorage에 loginUser 함수를 호출해서 id와 password를 prompt 창으로 받는다.
  .then(userStorage.getRoles)
  // resolve를 통해 나온 정보를 getRole이 받아서 id가 james 라면 resolve 실행.
  .then(id => alert(`Hello ${id.name}, you have a ${id.role} role`))
  // 받은 정보로 alert창을 띄움
  .catch(console.log);
  // 만약 james 가 아닌 다른 정보가 들어온다면, 콘솔창에 error 메세지

async 와 await

프라미스의 개념을 사용한 문법이지만, 보다 가독성 있게 코드를 표현할 수 있다.

비동기로 표현할 함수 앞에 async 라는 키워드를 붙이면, 해당 함수는 항상 프라미스를 반환.

비동기 함수를 호출하며, 호출 코드 앞에 await를 붙이면, 프라미스가 값을 반환하기 기다렸다가 반환되는 값을 변수에 할당해 준다.

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });

  let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)

  alert(result); // "완료!"
}

f();

자바스크립트는 await 키워드를 만나면 프라미스가 처리될 때까지 기다립니다(await는 '기다리다’라는 뜻을 가진 영단어입니다 – 옮긴이). 결과는 그 이후 반환. await는 async 함수 내부에서만 동작한다.

함수를 호출하고, 함수 본문이 실행되는 도중에 (*)로 표시한 줄에서 실행이 잠시 '중단’되었다가 프라미스가 처리되면 실행이 재개됩니다. 이때 프라미스 객체의 result 값이 변수 result에 할당.

따라서 위 예시를 실행하면 1초 뒤에 '완료!'가 출력

const A3 = async() => {
  const a = 'A 함수 실행 후 생기는 데이터';
  return a;
};
const B3 = (a) => {
  console.log(`${a}를 이용하는 B`)
};

const func = async () => {
  const b = await A3();
  console.log('A3 실행 성공!!!');
  B3(b);
};
func(); // A 함수 실행 후 생기는 데이터를 이용하는 B
  1. 변수 a를 정의하고 리턴하는 A3 함수
  2. 그 변수 a를 활용하는 B3 함수
  3. async를 통해 func를 실행하면, A3함수가 실행될 때 까지 기다렸다가 리턴되는 a 값을 변수 b가 받음
  4. A3 실행 성공!!! 출력
  5. A3의 리턴값을 받은 변수 b를 매개변수로 넣어 B3 함수 실행
  6. A 함수를 실행 후 생기는 데이터를 이용하는 B 출력

async await을 이용한 로그인 페이지 만들기 예제)

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if(
          (id === 'james' && password ==='asdf') || 
          (id === 'kody' && password === 'asdf') 
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 1000);
    });
  }

  getRoles(id) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if(id === 'james') {
          resolve({ name: 'james', role: 'admin' })
        } else {
          reject(new Error('no access'))
        }
      }, 1000)
    });
  }
}

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your password");

async function checkUser() {
  try {
    const userId = await userStorage.loginUser(id, password);
    const user = await userStorage.getRoles(userId);
    alert(`Hello ${user.name}, you have a ${user.role}`);
  } catch (error) {
    console.log(error);
  }
}
checkUser();

동기와 비동기 개념

동기 동작 순서

  1. 코드 실행
  2. 스택에 실행할 코드가 쌓인다.
  3. 쌓인 코드가 실행되고
  4. 실행된 코드는 스택에서 제거된다.

비동기란

  1. 요청을 보내면 응답에 상관 없이 다음 동작을 실행
  2. 동기와 반대 의미
  3. 병렬적
  4. Ajax, setTimeout

비동기 사용 방법

  1. callback
  2. promise
  3. Async Await

비동기 1 - callback

콜백함수

  1. 함수 실행 후 실행되는 함수
  2. 다른 함수에 파라미터로 넘겨지는 함수
  3. 이름없이 익명으로 전달된다.

예제코드1

function sum(a, b, callback) {
  let result = a + b;
  callback(result)
}

let callback = (result) => {
  console.log(result)
}
sum(10, 20, callback)
//30

예제코드2

function info (name, age, callback) {
  let result = {
    name: name,
    age: age
  }
  callback(result)
}

let print = function(result) {
  console.log(`${result.age}${result.name} 입니다.`)
}

info('Kody', 25, print)
//난 25살 Kody 입니다.

비동기 2 - promise

promise란?

  1. 비동기 실행 결과를 반환하는 객체 ⇒ 성공 실패 여부를 반환
  2. callback 지옥을 해결하기 위해 ES6부터 등장
  3. resolve, reject라는 인자를 전달하는 실행함수를 사용한다.

resolve

  • 비동기 작업 종료 시 resolve 호출하여 실행

reject

  • 비동기 작업 중간 오류 발생 시 reject 호출하여 실행

then( )

  • resolve로부터 나온 결과 값과 로직을 담은 것을 콜백 함수로 받는다.
  • resolve 시 실행

catch( )

  • reject에서 나온 예외 처리하는 로직을 콜백함수로 받는다.
  • 에러를 잡아줄 수 있음
  • reject시 실행

promise 상태

  1. pedding(대기) - 초기상태
  2. fullfilled(이행) - 성공
  3. rejected(거부) - 실패

예제코드 1

/* 프라미스를 이용한 비동기 1 */
function sum2(a, b) {
  return new Promise((resolve, reject) => {
    if(a==null || b==null) {
      reject(new Error(null))
    } else {
      resolve(a + b);
    }
  })
}
sum2(10, 20)
.then((result) => console.log(result))
.catch((error) => console.log("실패 " + error))
  1. 매개변수로 받은 a,b를 더한 결과 값을 프라미스로 받는 sum2
  2. sum2를 호출하면 프라미스가 반환
  3. 성공한 결과값은 resolve, error는 reject에 담아서 반환시킴
  4. resolve에서 반환된 프라미스는 then절을 통해 result로 받아서 console.log로 출력
  5. reject로 반환된 프라미스는 catch 절을 통해 error로 받아서 console.log 출력.

예제코드 2

/* 프라미스를 이용한 비동기 2 */
function info2(name, age) {
  return new Promise((resolve, reject) => {
    if(typeof name != 'string' || typeof age != 'number' || age<1 ) {
      reject(new Error(undefined))
    } else {
      resolve({
        name: name,
        age: age
      })
    }  
  })
}
info2('Kody', 25)
  .then((result) => console.log(`Name: ${result.name}, Age: ${result.age}`))
  .catch((error) => console.log("실패 " + error))
// Name: Kody, Age: 25

비동기3 - async / await

  • 비동기 처리 위해 사용
  • promise의 단점을 보완
  • callback hell 을 해결할 수 있다.

예제코드 1

/* async await 을 이용한 비동기 1 */
async function sum3(a, b) {
  let promise = new Promise((resolve, reject) => {
    if(a==null || b==null) {
      reject(new Error(null))
    } else {
      resolve(a + b);
    }
  })

  try{
    console.log(await promise)
  } catch(error) {
    console.log("실패 " +error)
  }
}
sum3(10, 20) // 30

예제코드 2

/* async await 을 이용한 비동기 2 */
async function info3(name, age) {
  let promise = new Promise((resolve, reject) => {
    if(typeof name != 'string' || typeof age != 'number' || age<1 ) {
      reject(new Error(undefined))
    } else {
      resolve({
        name: name,
        age: age
      })
    }  
  })
  
  try{
		let result = await promise;
    console.log(`Name: ${result.name}, Age: ${result.age}`)
  } catch(error) {
    console.log("실패 " + error)
  }
}
info3('Kody', 25) // Name: Kody, Age: 25

Promise VS. Await

function subtraction(a,b) {
  const test = new Promise((resolve, reject) => {
    if(a > b) resolve(a-b);
    else resolve(b-a)
  })
  return test
}

promise

function promiseTest() {
  return subtraction(10, 20)
    .then((result) => {
      console.log(result);
    })
		.catch((error) => {
			console.log(error);
};
promiseTest(); // 10

Async Await

async function promiseAsync() {
	try {
	  const result = await subtraction(10, 20);
	  console.log(result)
	} catch(error) {
		console.log(error)
	}
}
promiseAsync(); // 10
profile
일상 속 선한 영향력을 만드는 개발자를 꿈꿉니다🧑🏻‍💻

0개의 댓글

관련 채용 정보