JavaScript(8)

Minji Lee·2023년 10월 18일
0

javascript

목록 보기
10/11
post-thumbnail

Ch08 비동기

동기(Synchronous)와 비동기(Asynchronous)

동기: 순차적으로 코드 실행하는 방식

console.log(1);
console.log(2);
console.log(3);

/**
 * 1
 * 2
 * 3
 */

비동기: 순차적으로 코드 실행하지 않는 방식

console.log(1);
setTimeout(() => {
	console.log(2);
}, 1000);
console.log(3);

/**
 * 1
 * 3
 * 2
 */
  • btnEl은 버튼이 눌렸을 때 동작하므로 정확하게 언제 실행될지 모름 → 비동기 방식으로 동작

    const btnEl = document.querySelector('h1');
    btnEl.addEventListener("click", () => {
        console.log("Clicked");
    });
    console.log("Hello World!");
  • 먼저 작성한 코드는 fetch() 함수이지만, 1, 2, 3이 먼저 출력됨

    fetch(url)
        .then(res => res.json())
        .then(res => console.log(res));
    console.log(1);
    console.log(2);
    console.log(3);
    
    /**
     * 1
     * 3
     * 2
     * 특정 데이터
     */

fetch(): 특정한 웹 주소로 데이터를 전송할 때 사용
요청(request), 응답(response)

  • 인터넷 응답 속도에 따라 출력되는 시간이 달라짐

  • JS에서 비동기 코드는 실행하자마자 응답값이 도착하지 않아도 다음 코드로 넘어감

  • 응답값을 받은 후 코드 실행하는 방법

    fetch(url)
        .then(res => res.json())
        .then(res => {
            console.log(res);
            console.log(1);
            console.log(2);
            console.log(3);
        });
    
    /**
     * 특정 데이터
     * 1
     * 3
     * 2
     */

콜백(Callback) 패턴

동기 방식

const a = () => console.log(1);
const b = () => console.log(2);

a();
b();
/**
 * 1
 * 2
 */

비동기 방식

const a = (callback) => {
	setTimeout(() => {
		console.log(1);
		callback();
	}, 1000);
};
const b = () => console.log(2);

a();
b();
/**
 * 2
 * 1
 */

❗️ 비동기 방식에서 실행 순서를 보장하기 위해 콜백 패턴 사용

  • 비동기 코드는 서버로 언제 데이터를 받아오는지 알 수 없기 때문에 순서를 보장할 수 없음

콜백 패턴 장점: 순서 보장 가능
콜백 패턴 단점: 들여쓰기가 점점 깊어짐 → 콜백 지옥

⭐️ 콜백 지옥을 해결하기 위해 Promiseasync/await 사용!

ex) 영화 정보 검색

const getMovies = (movieName, callback) => {
  fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
    .then((response) => response.json())
    .then((res) => {
      console.log(res);
      callback();
    });
};

getMovies("frozen", () => {
  console.log("겨울왕국");
  getMovies("avengers", () => {
    console.log("어벤져스");
    getMovies("avatar", () => {
      console.log("아바타");
    });
  });
});

Promise

콜백 지옥을 해결해주는 클래스
Promise 클래스를 이용하여 메소드 체이닝을 통해 비동기 함수를 순차적으로 실행
new Promise((resolve) => resolve());

  • resolve 매개변수는 Promise 객체의 then 메소드 호출했을 때 콜백 함수 실행
const a = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1);
      resolve(); // 위의 코드가 실행된 후에 resolve()가 실행됨 -> 실행 위치 보장
    }, 1000);
  });
};

const b = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1);
      resolve(); // 위의 코드가 실행된 후에 resolve()가 실행됨 -> 실행 위치 보장
    }, 1000);
  });
};

const c = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1);
      resolve(); // 위의 코드가 실행된 후에 resolve()가 실행됨 -> 실행 위치 보장
    }, 1000);
  });
};

const d = () => console.log(4);

a()
  .then(() => {
    return b();
  })
  .then(() => {
    return c();
  })
  .then(() => {
    return d();
  });
  • resolve는 하나의 함수 데이터를 받음
// 아래와 같은 형태로 작성 가능
a()
  .then(b)
  .then(c)
  .then(d)
  .then(() => console.log("done"));

ex) 영화 정보 검색을 Promise 사용하여 작성

const getMovies = movieName => {
	return new Promise(resolve => {
    	fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
        .then(response => response.json())
        .then(res => {
        	console.log(res);
            resolve();
         });
    });
}

getMovies("frozen")
  .then(() => {
    console.log("겨울왕국");
    return getMovies("avengers");
  })
  .then(() => {
    console.log("어벤져스");
    return getMovies("avatar");
  })
  .then(() => {
    console.log("아바타");
  });

Async/Await

await: 그 뒤쪽에 있는 비동기 함수의 실행을 기다린다는 의미

  • 직관적이고 간결한 방식으로 비동기 코드 제어 가능
  • Promise 인스턴스가 반환되어야지만 앞에 붙여 사용 가능
  • await 키워드는 async 키워드가 붙은 함수 안에서만 사용 가능
  • await 키워드는 콘솔 로그 앞에서 사용 불가능 → 콘솔 로그는 프로미스 인스턴스를 반환하는 메소드가 아님!
const a = () => {
	return new Promise(resolve => {
    	setTimeout(() => {
        	console.log(1);
            resolve();
        }, 1000);
   });
};
const b = () => console.log(2);

const wrap = async () => {
	await a();
    b();
};

wrap();

ex) 영화 정보 검색을 async/await 사용하기

const getMovies = (movieName) => {
  return new Promise((resolve) => {
    fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
      .then((response) => response.json())
      .then((res) => {
        console.log(res);
        resolve();
      });
  });
};

const wrap = async () => {
	await getMovies('frozen');
    console.log('겨울왕국');
    await getMovies("avengers");
    console.log("어벤져스");
    await getMovies("avatar");
    console.log("아바타");
    
wrap();

에러 핸들링

에러를 처리하는 것

1. 콜백함수를 통한 처리

  • 정상적으로 로직이 동작하면 콜백함수를 통해 결과를 전달하고, 에러가 발생하면 에러 전달
const delayAdd = (index, cb, errorCb) => {
	setTimeout(() => {
    	if(index > 10) {
        	errorCb(`${index}는 10보다 클 수 없다.`);
            return;
        }
        console.log(index);
        cb(index+1);
   }, 1000);
};

delayAdd(4, (res) => console.log(res), (err) => console.error(err));
/*
 * 4
 * 5
 */

2. Promise의 resolve, reject를 통한 처리

  • resolve: 정상적으로 로직이 동작하면 콜백함수를 통해 결과 전달
  • reject: 에러가 발생하면 에러 전달
  • then().catch()를 통해 에러 핸들링
    - then 메소드에 있는 콜백함수는 resolve의 매개변수로 들어감
    - catch 메소드에 있는 콜백함수는 reject의 매개변수로 들어감
const delayAdd = (index) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			if(index > 10) {
				reject(`${index}는 10보다 클 수 없습니다.`);
				return;
			}
			console.log(index);
			resolve(index+1);
		}, 1000);
	});
};

delayAdd(4)
	.then((res) => console.log(res))
	.catch((err) => console.log(err));
  • then().catch().finally()를 통해 에러 핸들링 가능
  • finally 구문: 비동기 코드 내부의 성공여부와 상관없이 무조건 실행되는 구문
delayAdd(4)
  // then 메소드에 있는 콜백함수는 resolve의 매개변수로 들어감
  .then((res) => console.log(res))
  // catch 메소드에 있는 콜백함수는 reject의 매개변수로 들어감
  .catch((err) => console.error(err))
  .finally(() => console.log("Done"));

3. async/await를 통한 처리

  • try/catch를 통해 에러 핸들링

    const wrap = async () => {
        try {
            const res = await delayAdd(2);
            console.log(res);
        } catch (err) {
            console.error(err);
        }
    };
    
    wrap();
  • try/catch/finally

    const wrap = async () => {
      try {
        const res = await delayAdd(2);
        console.log(res);
      } catch (err) {
        console.error(err);
      } finally {
        console.log("Done");
      }
    };
    
    wrap();

ex) 영화 정보 검색 에러 핸들링

const getMovies = (movieName) => {
  return new Promise((resolve, reject) => {
    fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
      .then((res) => res.json())
      // catch로 걸러지지 않는 실질적인 에러를 처리하기 위해 if문을 통해 에러를 처리
      .then((json) => {
        if (json.Response === "False") {
          reject(json.Error);
        }
        resolve(json);
      })
      .catch((error) => {
        reject(error);
      });
  });
};

let loading = true;
  • then/catch/finally 구문 → .then()
    delayAdd(4)
      // then 메소드에 있는 콜백함수는 resolve의 매개변수로 들어감
      .then((res) => console.log(res))
      // catch 메소드에 있는 콜백함수는 reject의 매개변수로 들어감
      .catch((err) => console.error(err))
      .finally(() => console.log("Done"));
  • try/catch/finally 구문 → async/await
    const wrap = async () => {
      try {
        const movies = await getMovies("avengers");
        console.log("영화 목록: ", movies);
      } catch (error) {
        console.log("에러 발생: ", error);
      } finally {
        loading = false;
      }
    };

반복문에서 비동기 처리

const getMovies = (movieName) => {
  return new Promise((resolve) => {
    fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=${movieName}`)
      .then((res) => res.json())
      .then((res) => resolve(res));
  });
};

const titles = ["frozen", "avengers", "avatar"];

const wrap = async () => {
  for (const title of titles) {
    const movies = await getMovies(title);
    console.log(title, movies);
  }
};

wrap();
  • forEach 문 이용 → 반복을 매번 기다리면서 사용하는 경우에는 forEach 사용하면 안됨
titles.forEach(async (title) => {
  const movies = await getMovies(title);
  console.log(title, movies);
});
  • for문 이용
const wrap = async () => {
  for (const title of titles) {
    const movies = await getMovies(title);
    console.log(title, movies);
  }
};

wrap();

fetch

fetch(주소, 옵션)

네트워크를 통해 리소스의 요청(Request) 및 응답(Response)를 처리
→ Promise 인스턴스 반환

fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=frozen`)
  .then((res) => console.log(res.json()))
  .then((json) => console.log(json));
  • async/await 구문
const wrap = async () => {
  const res = await fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=frozen`);
  const json = await res.json();
  console.log(json);
};

wrap();

fetch 옵션

→ 요청에 필요한 옵션 전달

  • method 옵션: GET, POST, PUT, DELETE
  • headers 옵션: 서버 전송 요청
    - Content-Type: application/json: 서버로 전송되는 타입이 무엇인지 명시
  • body 옵션: 서버로 전송하고 싶은 데이터 명시
    - body로 전송되는 데이터는 반드시 문자열이어야 함
    - json 객체를 문자열로 변환하여 전송해야함(JSON.stringify())
fetch(`https://www.omdbapi.com/?apikey=7035c60c&s=frozen`, {
  method: `GET`,
  headers: {
    "Content-Type": "application/json",
  },e
  body: {
    name: "HEROPY",
    age: 85,
  },
})
  .then((res) => res.json())
  .then((json) => console.log(json));

0개의 댓글