비동기 (asynchronous)

Promise

  • 콜백 내에서 어떤 작업(예: 비동기 작업)을 수행하는 경우 모든 것이 순조롭게 작동하면 resolve가 호출되고 그렇지 않으면 reject가 호출된다
var promise = new Promise(function(resolve, reject) {
  // do something

  if (/* 잘 동작했다면 */) {
    resolve("성공");
  }
  else {
    reject(Error("실패"));
  }
});

promise.then(function(result) {
  console.log(result); // "성공"
}, function(err) {
  console.log(err); // Error: "실패"
});

  • promise는 다음 중 하나의 상태를 가진다
    • 처리됨(fulfilled) - 프라미스 관련 작업이 성공
    • 거부됨(rejected) - 프라미스 관련 작업이 실패
    • 대기중(pending): 이행 또는 거부되지 않은 초기 상태
    • 해결됨(settled) - 처리되거나 거부되었습니다
  • promise의 상태에 따른 흐름

promise

async/await

  • promise를 반환하는 getJSON 함수가 있다고 할 때, promise는 JSON 객체를 resolve한다
  • resolve한 결과로는 resolve된 JSON을 출력하고 “done” 문자열을 반환한다
const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log(data)
      return "done"
    });

makeRequest().then((result) => {
  // do something
})
  • 위 코드를 async/await로 작성하면 다음과 같다
const makeRequest = async () => {
  console.log(await getJSON())
  return "done"
}

makeRequest().then((result) => {
  // do something
})
  • async로 정의된 함수 내에서만 await를 사용할 수 있다
  • 모든 async 함수는 암시적으로 promise를 반환하며, promise의 resolve된 값은 함수에서 반환된 값이 된다 (예제에서는 “done” 문자열)
  • await getJSON()은 getJSON() promise가 resolve될 때까지 console.log() 호출을 기다린다는 의미이다

더 나은 이유?

간결하고 깔끔하다

  • then()을 사용하지 않아서 익명 함수를 만들고 data라는 변수를 선언하지 않아도 된다
  • nested function 사용을 피할 수 있다

try/catch로 error handling이 가능하다

// promise
const makeRequest = () => {
    try {
        getJSON()
        .then(result => {
            // 여기서 parse가 에러가 난다면
            const data = JSON.parse(result)
            console.log(data)
        })
        // promise의 catch()를 불러서 error handling을 해야한다
        // .catch((err) => {
        //   console.log(err)
        // })
    } catch (err) {
        console.log(err)
    }
}

// async/await
const makeRequest = async () => {
    try {
        // 여기서 parse가 에러가 난다면
        const data = JSON.parse(await getJSON())
        console.log(data)
    // catch block에서 error handling이 가능하다
    } catch (err) {
        console.log(err)
    }
}

조건부

// promise
const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}
  • nesting과 중괄호, return문이 많아 조건에 대한 결과를 예측하기 쉽지않다
// async/await
const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}
  • aync/await구문을 사용하면 조건에 대해서 더욱 읽기 쉬워진다

중간 값

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // do something
      return promise2(value1)
        .then(value2 => {
          // do something          
          return promise3(value1, value2)
        })
    })
}
  • promise1이 호출되고 그 return 값(중간 값)으로 promise2를 호출하고, 그 return 값으로 promise3를 호출한다
  • 위 예제의 nesting을 피하려면 Promise.all로 다시 작성할 수 있다
const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // do something
      return Promise.all([value1, promise2(value1)])
    })
    .then(([value1, value2]) => {
      // do something          
      return promise3(value1, value2)
    })
}
  • 이 코드를 async와 await로 사용하면 더 간단한 코드를 만들 수 있다
const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

에러 스택

  • 다음 코드처럼 여러 promise를 체이닝하여 호출하고 있는데 어딘가에서 에러가 발생한다고 가정해보자
const makeRequest = () => {
  return callAPromise()
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => {
      throw new Error("oops");
    })
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  })
  • promise의 체이닝 중에 에러가 발생한다면 callAPromise에 대해서만 나오기 때문에 에러난 위치에 대해 오해의 소지가 발생할 수 있다
const makeRequest = async () => {
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  throw new Error("oops");
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at makeRequest (index.js:7:9)
  })
  • async/await 구문으로 하게 되면 makeRequest에 대한 에러를 던져주기 때문에 디버깅할 때 더 도움이 된다

디버깅

const makeRequest = () => {
    return callAPromise()
        .then(() => CallAPromise())
        .then(() => CallAPromise())
        .then(() => CallAPromise())
        .then(() => CallAPromise())
}
  • 크롬 개발자 도구에서 디버깅을 할 때에는 동기 코드에 대해서만 디버깅을 할 수가 있어서 then을 따라 갈 수가 없다
const makeRequest = async () => {
    await CallAPromise()
    await CallAPromise()
    await CallAPromise()
    await CallAPromise()
}
  • 하지만 async/await 구문을 사용하면 정상적인 동기 호출인 것처럼 호출을 기다릴수 있다