자바스크립트 비동기 [3] – Async/Await, Promise Combinators: all, race, allSettled, any

try…catch로 에러 핸들링

try {
  let y = 1;
  const x = 2;
  y = 3;
} catch (err) {
  alert(err.message);
}
  • try 블록 안에서 발생한 에러를 핸들링할 코드를 catch 블록 안에 작성해주면 된다.

Async/Await

const getPosition = function () {
  return new Promise(function (resolve, reject) {
    navigator.geolocation.getCurrentPosition(resolve, reject);
  });
};

const whereAmI = async function () {
  try {
    // Geolocation
    const pos = await getPosition();
    const { latitude: lat, longitude: lng } = pos.coords;

    // Reverse geocoding
    const resGeo = await fetch(`https://geocode.xyz/${lat},${lng}?geoit=json`);
    if (!resGeo.ok) throw new Error('Problem getting location data');

    const dataGeo = await resGeo.json();
    console.log(dataGeo);

    // Country data
    const res = await fetch(
      `https://restcountries.com/v3.1/name/${dataGeo.country}`
    );
    
    if (!res.ok) throw new Error('Problem getting country');

    const data = await res.json();
    console.log(data);
    renderCountry(data[0]);
  } catch (err) {
    console.error(`${err} 💥`);
    renderError(`💥 ${err.message}`);
  }
};
whereAmI();
whereAmI();
whereAmI();
console.log('FIRST');
  • Promise 소비 코드를 조금 더 효율적으로 작성하기 위해 Async/Await을 사용할 수 있다.
  • async 키워드를 사용해서 함수를 작성하면 해당 함수는 백그라운드에서 실행되고, 완료되면 프로미스를 리턴한다.
  • async 안에 await 키워드를 사용하고 코드를 작성할 경우 프로미스가 fulfilled 상태가 되기 전에는 실행되지 않는다.
    • async 함수 전체 코드는 백그라운드에서 실행되기 때문에 메인 스레드를 블로킹하지는 않고, 내부에서 순차적으로 await 코드가 처리된다.
  • 프로미스에 then 메소드로 체이닝을 하는 것과 같은 원리로 동작하지만 코드를 읽기가 더 쉽다.

Async 함수로 값 리턴하기

const getJSON = function (url, errorMsg = 'Something went wrong') {
  return fetch(url).then(response => {
    if (!response.ok) throw new Error(`${errorMsg} (${response.status})`);

    return response.json();
  });
};

const getPosition = function () {
  return new Promise(function (resolve, reject) {
    navigator.geolocation.getCurrentPosition(resolve, reject);
  });
};

const whereAmI = async function () {
  try {
    // Geolocation
    const pos = await getPosition();
    const { latitude: lat, longitude: lng } = pos.coords;

    // Reverse geocoding
    const resGeo = await fetch(`https://geocode.xyz/${lat},${lng}?geoit=json`);
    if (!resGeo.ok) throw new Error('Problem getting location data');
    const dataGeo = await resGeo.json();

    // Country data
    const res = await fetch(
      `https://restcountries.com/v3.1/name/${dataGeo.country}`
    );
    if (!resGeo.ok) throw new Error('Problem getting country');
    const data = await res.json();
    renderCountry(data[0]);

    return `You are in ${dataGeo.city}, ${dataGeo.country}`;
  } catch (err) {
    console.error(`${err} 💥`);
    renderError(`💥 ${err.message}`);

    // Reject promise returned from async function
    throw err;
  }
};

console.log('1: Will get location');
// const city = whereAmI(); // 프로미스를 리턴함
// console.log(city);

// whereAmI()
//   .then(city => console.log(`2: ${city}`))
//   .catch(err => console.error(`2: ${err.message} 💥`))
//   .finally(() => console.log('3: Finished getting location'));

(async function () {
  try {
    const city = await whereAmI();
    console.log(`2: ${city}`);
  } catch (err) {
    console.error(`2: ${err.message} 💥`);
  }
  console.log('3: Finished getting location');
})();
  • async 함수는 프로미스를 리턴하기 때문에 단순히 값을 사용할 수 없다.
  • then 메소드를 사용해도 되나, async 함수 안에서 await 키워드로 받아서 사용하는 것이 조금 더 코드 가독성이 높다.
    • 실제 개발하면서도 async 안에서 async를 사용하는 코드를 작성할 일이 많을 것이다.
  • 또한 async 함수에서 에러가 발생하여서 정상적인 값을 반환받지 못했을 때에 대비한 코드를 작성해야 한다.

Promise 병렬 실행 콤비네이터: all

const getJSON = function (url, errorMsg = 'Something went wrong') {
  return fetch(url).then(response => {
    if (!response.ok) throw new Error(`${errorMsg} (${response.status})`);

    return response.json();
  });
};

const get3Countries = async function (c1, c2, c3) {
  try {
    // const [data1] = await getJSON(
    //   `https://restcountries.com/v3.1/name/${c1}`
    // );
    // const [data2] = await getJSON(
    //   `https://restcountries.com/v3.1/name/${c2}`
    // );
    // const [data3] = await getJSON(
    //   `https://restcountries.com/v3.1/name/${c3}`
    // );
    // console.log([data1.capital, data2.capital, data3.capital]);

    const data = await Promise.all([
      getJSON(`https://restcountries.com/v3.1/name/${c1}`),
      getJSON(`https://restcountries.com/v3.1/name/${c2}`),
      getJSON(`https://restcountries.com/v3.1/name/${c3}`),
    ]);
    console.log(data.map(d => d[0].capital));
  } catch (err) {
    console.error(err);
  }
};
get3Countries('portugal', 'canada', 'tanzania');
  • Promise.all을 사용하면 비동기 함수가 병렬로 실행된다.

다른 콤비네이터: race, allSettled, any

(async function () {
  const res = await Promise.race([
    getJSON(`https://restcountries.com/v3.1/name/italy`),
    getJSON(`https://restcountries.com/v3.1/name/egypt`),
    getJSON(`https://restcountries.com/v3.1/name/mexico`),
  ]);
  console.log(res[0]);
})();

const timeout = function (sec) {
  return new Promise(function (_, reject) {
    setTimeout(function () {
      reject(new Error('Request took too long!'));
    }, sec * 1000);
  });
};

Promise.race([
  getJSON(`https://restcountries.com/v3.1/name/tanzania`),
  timeout(5),
])
  .then(res => console.log(res[0]))
  .catch(err => console.error(err));
  • race는 말 그대로 여러 코드 중 가장 먼저 fulfilled 상태로 리턴된 프로미스를 반환하는 기능이다.
  • race는 타임아웃을 걸어야할 때 많이 사용하기도 한다.
Promise.allSettled([
  Promise.resolve('Success'),
  Promise.reject('ERROR'),
  Promise.resolve('Another success'),
]).then(res => console.log(res));

Promise.all([
  Promise.resolve('Success'),
  Promise.reject('ERROR'),
  Promise.resolve('Another success'),
])
  .then(res => console.log(res))
  .catch(err => console.error(err));
  • allSettled는 rejected 상태의 프로미스가 있어도 모든 결과를 배열로 반환한다. 반면 all은 rejected된 프로미스가 있으면 에러가 발생한다.
Promise.any([
  Promise.resolve('Success'),
  Promise.reject('ERROR'),
  Promise.resolve('Another success'),
])
  .then(res => console.log(res))
  .catch(err => console.error(err));
  • any의 경우 가장 먼저 fulfilled 상태로 반환된 프로미스를 하나만 리턴한다.
  • 모두 실패하면 AggregateError: All promises were rejected 에러가 발생한다.

Leave a Reply

Your email address will not be published. Required fields are marked *