Daily Front_Minhhk

[JS] 비동기[callBack, Promise, async-await] 본문

Code개발일지

[JS] 비동기[callBack, Promise, async-await]

Minhhk 2022. 11. 24. 00:31

학습 목표

  • 어떤 경우에 중첩된 콜백(callback)이 발생하는지 이해할 수 있다.
  • 중첩된 콜백(callback)의 단점, Promise의 장점을 이해할 수 있다.
  • async/await 키워드에 대해 이해하고, 작동 원리를 이해할 수 있다.

 

blocking vs non-blocking

  • 요청에 대한 결과가 동시에 일어난다 (synchronous) → 동기
  • 요청에 대한 결과가 동시에 일어나지 않는다 (asynchronous) → 비동기

asynchronous 예제

function waitAsync(callback, ms) {
  setTimeout(callback, ms); // 특정 시간 이후에 callback 함수가 실행되게끔 하는 브라우저 내장 기능입니다
}

function drink(person, coffee) {
  console.log(person + '는 ' + coffee + '를 마십니다');
}

function orderCoffeeSync(coffee) {
  console.log(coffee + '가 접수되었습니다');
  waitSync(2000);
  return coffee;
}

let customers = [{
  name: 'Steve',
  request: '카페라떼'
}, {
  name: 'John',
  request: '아메리카노'
}];

function orderCoffeeAsync(menu, callback) {
  console.log(menu + '가 접수되었습니다');
  waitAsync(function() {
    callback(menu);
  }, 2000);
}

// call asynchronously
customers.forEach(function(customer) {
  orderCoffeeAsync(customer.request, function(coffee) {
    drink(customer.name, coffee);
  });
});

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();

// A
// B
// C

Promise

  • 프로미스는 자바스크립트 비동기 처리에 사용되는 객체
  • 여기서 자바스크립트의 비동기 처리란
  • ‘특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성’
function getData() {
  return new Promise(function(resolve, reject) {
    $.get('url 주소/products/1', function(response) {
      if (response) {
        resolve(response);
      }
      reject(new Error("Request is failed"));
    });
  });
}

// 위 $.get() 호출 결과에 따라 'response' 또는 'Error' 출력
getData()
	.then(function(data) {
	  console.log(data); // response 값 출력
	})
	.catch(function(err) {
	  console.error(err); // Error 출력
	});

Pending(대기)

먼저 아래와 같이 new Promise() 메서드를 호출하면 대기(Pending) 상태가 됩니다.

new Promise();

new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject입니다.

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

Fulfilled(이행)

여기서 콜백 함수의 인자 resolve를 아래와 같이 실행하면 이행(Fulfilled) 상태가 됩니다.

new Promise(function(resolve, reject) {
  resolve();
});

그리고 이행 상태가 되면 아래와 같이 then()을 이용하여 처리 결과 값을 받을 수 있습니다.

function getData() {
  return new Promise(function(resolve, reject) {
    var data = 100;
    resolve(data);
  });
}

// resolve()의 결과 값 data를 resolvedData로 받음getData().then(function(resolvedData) {
  console.log(resolvedData);// 100});

※ 프로미스의 '이행' 상태를 좀 다르게 표현해보면 '완료' 입니다.

Rejected(실패)

new Promise()로 프로미스 객체를 생성하면 콜백 함수 인자로 resolve와 reject를 사용할 수 있다고 했습니다. 여기서 reject를 아래와 같이 호출하면 실패(Rejected) 상태가 됩니다.

new Promise(function(resolve, reject) {
  reject();
});

그리고, 실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있습니다.

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}

// reject()의 결과 값 Error를 err에 받음getData().then().catch(function(err) {
  console.log(err);// Error: Request is failed});

.catch( () ⇒ {} )

더 많은 예외 처리 상황을 위해 프로미스의 끝에 가급적 catch() ㄱㄱ

// catch()로 오류를 감지하는 코드
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData().then(function(result) {
  console.log(result); // hi
  throw new Error("Error in then()");
}).catch(function(err) {
  console.log('then error : ', err); // then error :  Error: Error in then()
});

Promise Chaining

function getData() {
  return new Promise({
    // ...
  });
}

// then() 으로 여러 개의 프로미스를 연결한 형식
getData()
  .then(function(data) {
    // ...
  })
  .then(function() {
    // ...
  })
  .then(function() {
    // ...
  });

ex

new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve(1);
  }, 2000);
})
.then(function(result) {
  console.log(result); // 1
  return result + 10;
})
.then(function(result) {
  console.log(result); // 11
  return result + 20;
})
.then(function(result) {
  console.log(result); // 31
});

Promise.all 사용하기

Promise.all은 배열 내 모든 값의 이행(또는 첫 번째 거부)을 기다립니다.

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 100);
});

Promise.all([p1, p2, p3]).then(values => {
  console.log(values); // [3, 1337, "foo"]
});

 


async await

async & await 기본 문법

  • async & await을 사용하면 동기적으로 코드를 짜는 것처럼 비동기 처리를 할 수 있다.
  • function 앞에 async를 붙이면 코드 블럭안이 Promise로 자동 변환
async function 함수명() {
  await 비동기_처리_메서드_명();
}
// 함수 선언식
async function funcDeclarations() {
	await 작성하고자 하는 코드
	...
}

// 함수 표현식
const funcExpression = async function () {
	await 작성하고자 하는 코드
	...
}

// 화살표 함수
const ArrowFunc = async () => {
	await 작성하고자 하는 코드
	...
}

복잡한 Promise 코드를 간결하게 작성할 수 있게 되었습니다.

함수 앞에 async 키워드를 사용하고 async 함수 내에서 await 키워드를 사용하면 됩니다.

이렇게 작성된 코드는 await 키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작하게 됩니다.

const printString = (string) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
      console.log(string);
    }, Math.floor(Math.random() * 100) + 1);
  });
};

const printAll = async () => {
  await printString('A');
  await printString('B');
  await printString('C');
};

printAll();

console.log(
  `Async/Await을 통해 Promise를 간결한 코드로 작성할 수 있게 되었습니다.`
);
function gotoCodestates() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('1. go to codestates')}, 100)
  })
}

function sitAndCode() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('2. sit and code')}, 100)
  })
}

function eatLunch() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('3. eat lunch')}, 100)
  })
}

function goToBed() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('4. go to bed')}, 100)
  })
}

// async await
const result = async() => {

  const one = await gotoCodestates();
  console.log(one)

  const two = await sitAndCode()
  console.log(two)

  const three = await eatLunch()
  console.log(three)

  const four = await goToBed()
  console.log(four)
}

result()

// 1. go to codestates
// 2. sit and code
// 3. eat lunch
// 4. go to bed

async & await 예외 처리

async & await에서 예외를 처리하는 방법은 바로 try catch입니다.

프로미스에서 에러 처리를 위해 .catch()를 사용했던 것처럼

async에서는 catch {} 를 사용하시면 됩니다.

조금 전 코드에 바로 try catch 문법을 적용해보겠습니다.

async function logTodoTitle() {
  try {
    var user = await fetchUser();
    if (user.id === 1) {
      var todo = await fetchTodo();
      console.log(todo.title);// delectus aut autem}
  } catch (error) {
    console.log(error);
  }
}

위의 코드를 실행하다가 발생한 네트워크 통신 오류뿐만 아니라 간단한 타입 오류 등의 일반적인 오류까지도 catch로 잡아낼 수 있습니다.

발견된 에러는 error 객체에 담기기 때문에 에러의 유형에 맞게 에러 코드를 처리해주시면 됩니다.