JS DeepDive - async/await

이상철·2023년 5월 1일
0

JS - DeepDive

목록 보기
2/8
post-thumbnail
  • async/await는 프로미스를 기반으로 동작합니다.
  • 비동기 처리 결과를 후속 처리 없이 마지 동기 처리처럼 프로미스를 사용할 수 있습니다.
  • 프로미스의 후속 처리 메서드 없이 마치 동기 처리처럼 프로미스가 처리 결과를 반환하도록 구현할 수 있다.
const getRequestDataUseFetch = async() => {
	const url = "https://jsonplaceholder.typicode.com/comments/1"

	const response = await fetch(url);
	const comments = await response.json()
	console.log(comments)
}

getRequestDataUseFetch()

/*
{
  postId: 1,
  id: 1,
  name: 'id labore ex et quam laborum',
  email: 'Eliseo@gardner.biz',
  body: 'laudantium enim quasi est quidem magnam voluptate ipsam eos
' +
    'tempora quo necessitatibus
' +
    'dolor quam autem quasi
' +
    'reiciendis et nam sapiente accusantium'
}
*/

Async 함수

  • await 키워드는 반드시 async 함수 내부에서 사용해야 한다. async 함수는 async 키워드를 사용해 정의하며 언제나 프로미스를 반환한다.
  • async 함수가 명시적으로 프로미스를 반환하지 않더라도 async 함수는 암묵적으로 반환값을 resolve하는 프로미스를 반환한다.
// 화살표 함수 
const foo = async(n) => {return n}
	foo(1).then(res => console.log(res)) // 1

// 함수 선언문
async function bar(n) {
	return n;
}
bar(2).then(function(n){console.log(n)}) // 2

// 함수 표현식
const baz = async function(n) {return n}
baz(3).then(function(n){console.log(n)}) // 3

// async 메소드
const obj = {
	async foo(n) {
		return n
	}
}
obj.foo(4).then(res => console.log(res))

// async class method
class AsyncClass {
	async getPromise (n) {
		return n
	}
}

const getNumber = new AsyncClass()
getNumber.getPromise(5).then(res => console.log(res))
  • 단, 클래스의 constructor 메소드는 async 메서드가 될 수 없다. 클래스의 constructor 메소드는 인스턴스를 반환해야 하지만 async 함수는 언제나 프로미스를 반환해야 한다.
class AsyncClass {
	async constructor() {
	}
} 

const getClass = new AsyncClass();
// SyntaxError: Constructor can't be an async function.

await 키워드

  • await 키워드는 프로미스가 settled 상태(비동기 처리가 수행된 상태)가 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환한다.
  • await 키워드는 반드시 프로미스 앞에서 사용해야 한다.
const getTodosData = async(id) => {
	const url = `https://jsonplaceholder.typicode.com/todos/${id}`
	
	const response = await fetch(url); "1번"
	const todoData = await response.json(); "2번"
	console.log(todoData)
}

getTodosData(5)

/*
	{
	  userId: 1,
	  id: 5,
	  title: 'laboriosam mollitia et enim quasi adipisci quia provident illum',
	  completed: false
	}
*/
  • await 키워드는 프로미스가 settled 상태가 될 때까지 대기
  • 1번의 함수가 수행한 HTTP 요청에 대한 서버의 응답이 도착해서
  • fetch 함수가 반환한 프로미스가 settled 상태가 될 때까지 1번에서 대기.
  • 이후 프로미스가 settled 상태가 되면 프로미스가 resolve한 처리 결과가 response 결과에 할당된다.
  • 이 처럼 await 키워드는 다음 실행을 일시 중단 시켰다가 프로미스가 settled상태가 되면 다시 재개한다.
const getTimerData = async() => {
	const one = await new Promise(
		(res) => setTimeout(() => {return res(10)},3000)
	)
	console.log(one) // 3초 소요

	const two = await new Promise(
		(res) => setTimeout(() => {return res(20)}, 2000)
	)
	console.log(two) // 2초 소요

	const three = await new Promise(
		(res) => setTimeout(() => {return res(30)}, 1000)	
	)
	console.log(three); // 1초 소요

	console.log([one,two,three]) // [10, 20, 30]  총 6초 소요
}

* one 상수에서 동작하는 프로미스 객체가 값을 리턴하여 one 상수에 할당 되기까지 하위 코드는 멈춤
* 상수 one에 3초뒤 10이 할당되면 다음 코드로 넘어가서 콘솔 동작 // 10
* 상수 two에서 동작하는 프로미스 객체가 값을 리턴하여 two 상수에 할당 되기까지 하위 코드 멈춤 
* 상수 two에 2초뒤 20이 할당되면 하위 코드로 넘어가 콘솔 동작 // 20
* 상수 three에서 동작하는 프로미스 객체가 값을 리턴하여 three 상수에 할당 되기까지 하위코드 멈춤
* 상수 three에 1초뒤 30이 할당되면 하위코드로 넘어가 콘솔 동작 // 10
* console.log([one,two,three]) //[10,20,30] 출력까지 총 6초 소요
  • 모든 프로미스에 await 키워드를 사용하는 것은 주의해야한다.
  • getTimerData 함수가 수행하는 3개의 비동기 처리는 서로 연관이 없이 개별적으로 수행되는 비동기 처리 이므로 앞선 비동기 처리가 완료될 때까지 대기해서 순차적으로 처리할 필요는 없다.
  • 아래와 같이 개선하면 좋다.
const getTimerData = async() => {
	const res = await Promise.all([
		new Promise(resolve => setTimeout(() => {return resolve(10)},3000)),
		new Promise(resolve => setTimeout(() => {return resolve(20)},2000)),
		new Promise(resolve => setTimeout(() => {return resolve(30)},1000))
	])
	console.log(res)
}
getTimerData() // 약 3초 소요
  • 다음 예제는 앞선 비동기 처리의 결과를 가지고 다음 비동기를 처리해야한다. 따라서 비동기 처리의 처리 순서가 보장되어야 하므로 모든 프로미스에 await 키워드를 써서 순차적으로 처리해야한다.
const bar = async(n) => {
	const one = await new Promise(
		(res) => setTimeout(() => {return res(n)},3000)
	)
	const two = await new Promise(
		(res) => setTimeout(() => {return res(one + 1)},2000)
	)

	const three = await new Promise(
		(res) => setTimeout(() => {return res(two + 1)},1000)
	)

	console.log([one, two, three])
}

bar(10) // [10,11,12] 6초 소요

에러 처리

  • 비동기 처리를 위한 콜백 패턴의 단점 중 가장 심각한 것은 에러 처리가 곤란하다는 것.
  • async/await 에서 에러처리는 try…catch 문을 사용할 수 있다.
const getData = async() => {
	try{
		const wrongUrl = "https://jsonplaceholder.typicode.comXXXXX/todos/1";
		const response = await fetch(wrongUrl)
		const data = await response.json()
		console.log(data)
	}catch(e) {
		console.log("Error!" , e)
	}
}

getData() // 'Error!' Error: 'Failed to fetch'
  • 위 예제의 getData 함수의 catch 문은 HTTP 통신에서 발생한 네트워크 에러뿐만 아니라 try 코드 블록 내의 모든 문에서 발생한 일반적인 에러까지 모두 캐치할 수 있다.
  • async 함수 내에서 catch 문을 사용해서 에러 처리를 하지 않으면 async함수는 발생한 에러를 reject하는 프로미스를 반환한다.
  • 따라서, async 함수를 호출하고 Promise.prototype.catch 후속 처리 메서드를 사용하여 에러를 캐치할 수 있다.
const getData = async() => {
	const wrongUrl = "https://jsonplaceholder.typicode.comXXXXX/todos/1";
	const response = await fetch(wrongUrl)
	const data = await response.json()
	return data
}

getData()
.then((res) => console.log(res))
.catch((e) => console.log("Error !", e)) // 'Error !' Error: 'Failed to fetch'

끄적끄적..

const get = async(n) => {
  try{
    const res = await Promise.allSettled([
       new Promise (res => {
          const ret =  fetch(`https://jsonplaceholder.typicode.com/comments/${n}`)
          ret.then((res) => res.json()).then((res) => console.log(res))
          res(1)
         }
        ),
       new Promise (res => {
           const ret = fetch(`https://jsonplaceholder.typicode.com/photos/${n}`)
           ret.then((res) => res.json()).then((res) => console.log(res))
           res(2)
          } 
        ),
        new Promise (res => {
            const ret = fetch(`https://jsonplaceholder.typicode.com/todos/${n}`)
            ret.then((res) => res.json()).then((res) => console.log(res))
            res(3)
          }  
        )
	   ])
   return res
  }catch(e) {
    console.log("Error" , e)
  }
}

get(1).then((res) => console.log(res))

/*
	[
	  { status: 'fulfilled', value: 1 },
	  { status: 'fulfilled', value: 2 },
	  { status: 'fulfilled', value: 3 }
	]
*/
profile
헤더부터 푸터까지!!!

0개의 댓글