Asynchronous JavaScript

Asynchronous Callback

  • 함수를 호출할 때, 콜백까지 같이 인자에 넣어서 호출하는 비동기 프로그래밍 양식
  • 콜백에서 에러 인자를 받는 방식으로 에러 처리를 함
  • Node.js 내장 모듈 전체가 이 방식을 사용하도록 만들어져 있음

주의! 모든 콜백이 비동기인 것은 아님


> [1,2,3].map(x => x*x)
[ 1, 4, 9 ]
        

readFile


// readFile.js
const fs = require('fs') // Node.js 내장 모듈
fs.readFile('./calc.js', 'utf8', (err, data) => {
  console.log(data)
})
console.log('done!')
        

// readFileSync.js
const fs = require('fs') // Node.js 내장 모듈
const data = fs.readFileSync('./calc.js', 'utf8')
console.log(data)
console.log('done!')
        

request 설치


$ # hello-npm 폴더 안에서 실행
$ npm install --save request
        

Github REST API 호출


// 유저 이름, 저장소, 할당된 이슈 갯수 출력하기
const request = require('request')
const apiUrl = 'https://api.github.com'
const option = {
  json: true,
  auth: {
    'user': 'username',
    'pass': 'password',
  },
  headers: {
    'User-Agent': 'request'
  }
}
request.get(`${apiUrl}/user`, option, function (error, response, body) {
  const name = body.name
  if (error) console.error(error)
  // 콜백 안에 콜백
  request.get(`${apiUrl}/user/repos`, option, function (error, response, body) {
    if (error) console.error(error)
    const repoNames = body.map(item => item.name)
    // 콜백 안에 콜백 안에 콜백
    request.get(`${apiUrl}/issues`, option, function (error, response, body) {
      if (error) console.error(error)
      const issueNum = body.length
      console.log(`name: ${name}`)
      console.log('repos:')
      repoNames.forEach(name => {
        console.log(name)
      })
      console.log(`num of assigned issues: ${issueNum}`)
    })
  })
})
        

Callback Hell

Promise

비동기 작업의 결과를 담는 객체

정확히 언제가 될지 모르지만, 결국 성공 또는 실패의 상태를 갖게 됨


// tenSec.js
module.exports = function tenSec(value) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value)
    }, 10000)
  })
}
        

// REPL
> const tenSec = require('./tenSec')
> const p = tenSec(1)
> p // 만든지 10초가 지나기 전
Promise {
  [pending],
  ...
> p // 만든지 10초가 지난 후
Promise {
  1,
  ...
        

.then


> tenSec('hello promise').then(value => {
... console.log(value)
... })
Promise { // `then`은 Promise를 반환
  [pending],
  ...
> // 10초 후
'hello promise'
        

Promise chaining


// chaining.js
const tenSec = require('./tenSec')
tenSec('hello promise')
  .then(value => {
    console.log(value)
    return 1 // 위 `.then`은 값이 1인 Promise를 반환함
  })
  .then(value => {
    console.log(value)
    return tenSec('new promise') // Promise도 반환할 수 있음
  })
  .then(value => {
    console.log(value)
  })
  .then(() => {
    throw new Error('error in promise')
  })
  .catch(err => {
    console.error(err)
  })
  .then(() => { // 에러 처리 이후에도 코드 실행 가능
    console.log('done')
  })
        

readFile - promise


// readfilePromise.js
const {promisify} = require('util') // Node.js 8.0.0부터 추가됨
const fs = require('fs')
const readFile = promisify(fs.readFile)
readFile('./calc.js', 'utf8')
  .then(data => {
    console.log(data)
  })
  .catch(err => {
    console.error(err)
  })
        

Promise의 특징

이미 resolve 된 Promise에도 콜백을 실행할 수 있음


> const resolved = Promise.resolve(1)
> resolved.then(v => console.log(v))
        

`.then`에 넘겨진 콜백은 무조건 다음 루프에 실행됨


> (function() {
... Promise.resolve(1).then(v => console.log(v))
... console.log('done!')
... })()
/* 출력:
done!
1
*/
        

Promise.all


// npm install --save request-promise
const rp = require('request-promise')
const apiUrl = 'https://api.github.com'
const option = {
  json: true,
  auth: {
    'user': 'username',
    'pass': 'password',
  },
  headers: {
    'User-Agent': 'request'
  }
}

const userPromise = rp.get(`${apiUrl}/user`, option)
const reposPromise = rp.get(`${apiUrl}/user/repos`, option)
const issuesPromise = rp.get(`${apiUrl}/issues`, option)

// 배열 내의 모든 Promise 객체가 완료되었을 때
// resolve 되는 Promise를 만든다.
Promise.all([userPromise, reposPromise, issuesPromise])
  .then(([user, repos, issues]) => {
    console.log(`name: ${user.name}`)
    console.log('repos:')
    repos.forEach(repo => {
      console.log(repo.name)
    })
    console.log(`num of assigned issues: ${issues.length}`)
  })
        

Async/Await


const tenSec = require('./tenSec')

async function resolveAfterTenSec() {
  await tenSec()
  return 1
}

resolveAfterTenSec().then(value => {
  console.log(value)
})
        
  • ES2017에서 도입되어, 비동기식 코드를 동기식 코드처럼 쓸 수 있는 문법 제공
  • Chrome 55, Node.js 8.0.0 부터 사용가능
  • async function 안에서 반환된 값은 최종적으로 Promise 객체로 변환되어 반환된다.
  • async function 안에서 쓸 수 있는 await 키워드는 현재 함수를 중단시키고 Promise 객체가 충족될 때까지 기다리지만, 스레드를 block 하지 않는다.
  • 에러 처리는 동기식 코드처럼 try, catch 블록을 통해서 한다.

readFile - async/await


// readfileAsync.js
const {promisify} = require('util') // Node.js 8.0.0부터 추가됨
const fs = require('fs')
const readFile = promisify(fs.readFile)

async function readFileAsync() {
  try {
    const data = await readFile('./calc.js', 'utf8')
    console.log(data)
  } catch (e) {
    console.error(e)
  }
}

readFileAsync()
        

수고하셨습니다