Skip to content

Latest commit

 

History

History
executable file
·
455 lines (374 loc) · 12.1 KB

16. promise.md

File metadata and controls

executable file
·
455 lines (374 loc) · 12.1 KB

16. Promise

16-1. 소개

Callback Hell

  • id가 'btn'인 button을 클릭하면 서버에 users 리스트를 가져오는 요청을 하고,
  • 성공하면 list의 세번째 user의 정보를 다시 요청하여
  • 성공하면 user의 profileImage url값을 가져다가 image 태그로 표현하고,
  • 이 image를 클릭하면 해당 이미지를 제거.
const script= document.createElement('script')
script.src= 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'
document.body.appendChild(script)

document.body.innerHTML += '<button id="btn">클릭</button>'
document.getElementById('btn').addEventListener('click', function (e) {
  $.ajax({
    method: 'GET',
    url: 'https://api.github.com/users?since=1000',
    success: function (data) {
      var target = data[2]
      $.ajax({
        method: 'GET',
        url: 'https://api.github.com/user/' + target.id,
        success: function (data) {
          var _id = 'img' + data.id
          document.body.innerHTML += '<img id="' + _id + '" src="' + data.avatar_url + '"/>'
          document.getElementById(_id).addEventListener('click', function (e) {
            this.remove()
          })
        },
        error: function (err) {
          console.error(err)
        }
      })
    },
    error: function (err) {
      console.error(err)
    }
  })
})

Promise

document.body.innerHTML = '<button id="btn">클릭</button>'
document.getElementById('btn').addEventListener('click', function (e) {
    fetch('https://api.github.com/users?since=1000')
    .then(function (res) { return res.json() })
    .then(function (res) {
        var target = res[2]
        return fetch('https://api.github.com/user/' + target.id)
    })
    .then(function (res) { return res.json() })
    .then(function (res) {
        var _id = 'img' + res.id
        document.body.innerHTML += '<img id="' + _id + '" src="' + res.avatar_url + '"/>'
        document.getElementById(_id).addEventListener('click', function (e) {
            this.remove()
        })
    })
    .catch(function (err) {
        console.error(err)
    })
})

Promise를 반환하면서 JSON parsing을 자동으로 해주는 library (axios) 활용시

const script= document.createElement('script')
script.src= 'https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js'
document.body.appendChild(script)

document.body.innerHTML += '<button id="btn">클릭</button>'
document.getElementById('btn').addEventListener('click', function (e) {
    axios.get('https://api.github.com/users?since=1000')
    .then(function (res) {
        var target = res.data[2]
        return axios.get('https://api.github.com/user/' + target.id)
    })
    .then(function (res) {
        var _id = 'img' + res.data.id
        document.body.innerHTML += '<img id="' + _id + '" src="' + res.data.avatar_url + '"/>'
        document.getElementById(_id).addEventListener('click', function (e) {
            this.remove()
        })
    })
    .catch(function (err) {
        console.error(err)
    })
})

16-2. 상세

16-2-1. Promise Status

  • unnsettled (미확정) 상태: pending. thenable하지 않다.
  • settled (확정) 상태: resolved. thenable한 상태. (문맥에 따라 다르니 조심할 것)
    • fulfilled (성공)
    • rejected (실패)
const promiseTest = param => new Promise((resolve, reject) => {
	setTimeout(() => {
		if (param) {
			resolve("해결 완료")
		} else {
			reject(Error("실패!!"))
		}
	}, 1000)
})

const testRun = param => promiseTest(param)
  .then(text => { console.log(text) })
  .catch(error => { console.error(error) })

const a = testRun(true)
const b = testRun(false)

16-2-2. 문법

  • new Promise(function)
  • .then(), .catch()는 언제나 promise를 반환한다.
const executer = (resolve, reject) => { ... }
const prom = new Promise(executer)

const onResolve = res => { ... }
const onReject = err => { ... }

// (1)
prom.then(onResolve, onReject)

// (2)
prom.then(onResolve).catch(onReject)
new Promise((resolve, reject) => { ... })
.then(res => { ... })
.catch(err => { ... })
const simplePromiseBuilder = value => {
  return new Promise((resolve, reject) => {
    if(value) { resolve(value) }
    else { reject(value) }
  })
}

simplePromiseBuilder(1)
  .then(res => { console.log(res) })
  .catch(err => { console.error(err) })

simplePromiseBuilder(0)
  .then(res => { console.log(res) })
  .catch(err => { console.error(err) })
const simplePromiseBuilder2 = value => {
  return new Promise((resolve, reject) => {
    if(value) { resolve(value) }
    else { reject(value) }
  })
  .then(res => { console.log(res) })
  .catch(err => { console.error(err) })
}

simplePromiseBuilder2(1)
simplePromiseBuilder2(0)
// 프로미스 실행순서 

// resolve 우선 실행시
const prom = new Promise((resolve, reject) => {
  resolve()
  reject()
  console.log('Promise')
})
prom.then(() => {
  console.log('then')
})

prom.catch(() => {
  console.log('catch')
})

console.log('Hi!')


>> "Promise", "Hi", "then"

>> 전체소스실행하는 과정에서 Promise 인스턴스의 함수도 같이 실행되었다.
-> fullfilled 되면서 then 함수가 queue에 추가됬고
-> 계속 전체소스 실행이 끝나고 나서 
-> 다음번 Q에 있는 then 함수가 실행됨
-> reject는 무시되었네?
  
// reject 우선 실행시
const prom = new Promise((resolve, reject) => {
  reject()
  resolve()
  console.log('Promise')
})
prom.then(() => {
  console.log('then')
})

prom.catch(() => {
  console.log('catch')
})

console.log('Hi!')

>> "Promise", "Hi", "catch"

>> 전체소스실행하는 과정에서 Promise 인스턴스의 함수도 같이 실행되었다.
-> fullfilled 되면서 then 함수가 queue에 추가됬고
-> 계속 전체소스 실행이 끝나고 나서 
-> 다음번 Q에 있는 then 함수가 실행됨
-> resolve는 무시되었네?

위 실행으로 알게된 사실은 다음과 같다.

  1. then 이나 catch 구문은 실행큐에 후순위로 등록되고 실행된다.

  2. promise 인스턴스에 넘긴 함수 내부에서는 resolve나 reject둘 중에 먼저 호출된 것만 실제로 실행된다.

  3. 사실 실제로 실행되는게 아니라, 실행은 둘다 되는데, 사실 먼저 둘중 하나를 실행하고 나면 pending의 상태가 fullfilled 로 변하고 현재 Promise의 상태를 끝내라라고 함, pending의 상태가 변하면 다음의 resolve, reject는 의미가 없어짐 그래서

  4. 결론적으로 사실 실제로 실행되는게 아니라, 실행은 둘다 되는데, pending 상태일 때만 의미가 있기 때문 2번과 같은 결과가 나온다.

16-2-3. 확장 Promise 만들기

  1. Promise.resolve, Promise.reject
// 뒤를 thenable 하게 만들려고 한 함수
Promise.resolve(42)
.then(res => { console.log(res) })
.catch(err => { console.error(err) })

Promise.reject(12)
.then(res => { console.log(res) })
.catch(err => { console.error(err) })
  1. thenable 객체
    • thenalbe 은 사실 인터페이스 (duck type )
    • then 이라는 method를 가지고 있고, 그 메소드가 resolve 혹은 reject를 실행할 수 있게 되었다면 thenable 하다라고 할 수 있음
    • iterator 랑 비슷함
const thenable = {
  then (resolve, reject) {
    resolve(33)
  }
}
const prom1 = Promise.resolve(10)
// Promise.{<resolved>: 10}

const prom = Promise.resolve(thenable)
// 이미 >Promise.{<resolved>: 10} 형태로 then 실행도 안했는데 33을 가지고 있음 

// resolve를 시킨다는 건 뭐냐면 
// 인자로 thenable 객체가 전달되면, 해당 객체의 then 메소드를 호출해서 resolve 된 결과를 반환
// 인자로 값이 전달되면, 그 값을 resolve 상태로 만듬 

prom.then(res => { console.log(res) })
// 그럼 catch 도 되욤? 안됨 
const thenable = {
  then (resolve, reject) {
    resolve(33)
  }
}

const prom = Promise.resolve(thenable)

const thenable = {
  then (resolve, reject) {
    reject(33)
  }
}
const prom = Promise.resolve(thenable)
prom.catch(err => { console.log(err) })

16-2-4. Promise Chaning (then, catch에서 return)

    1. return 일반값 : promise 객체에 resolved 상태로 반환함. 그 안에 값이 담김 Promise(<<resolved>> : 값) 형태로 됨
    1. return promise 인스턴스 promise 인스턴스가 리턴된 것
    • 프로미스에 인스턴스가 리턴됬으니까 리턴된 애도 프로미스
    • 반환된 프로미스 인스턴스도 언젠가 pending에서 resolved 로 바뀔꺼고 다시 then을 태울 수 있음
    1. return 을 안하면 return undefined (원래 JS 동작이 이러함) undefined는 일반값이므로 1) 과 동일한 결과
    1. Promise.resolve(), or Promise.reject() : return 해주지 않는 이상 의미없음
    • 그냥 별개의 프로미스 객체가 생성될 뿐, 현재 process 상의 Promise 플로우에 영향을 주지 않음
    • 그냥 새로운 promise 일뿐 시퀀스를 따라가지 않음
    • 다만 return을 하면 위의 2) 의 new Promise 한것과 동일한 결과
new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('첫번째 프라미스')
  }, 1000)
}).then(res => {
  console.log(res)
  return '두번째 프라미스'
}).then(res => {
  console.log(res)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
          resolve('세번째 프라미스')
    }, 1000)
  })
}).then(res => {
  console.log(res)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
          reject('네번째 프라미스')
    }, 1000)
  })
}).then(res => {
  console.log(res)
}).catch(err => {
  console.error(err)
  return new Error('이 에러는 then에 잡힙니다.')
}).then(res => {
  console.log(res)
  // // throw 하면 캐치로 가지만, 프로미스에서는 then 건너뛰고 캐치로 감
  throw new Error('이 에러는 catch에 잡힙니다.') 
}).then(res => {
  console.log('출력 안됨')
}).catch(err => {
  console.error(err)
})
  • 에러가 나도 여전히 프로미스 객체는 종료되지 않음
  • 단지 에러가 났다는 사실을 이해할 수 있음

16-2-5. Error Handling

asyncThing1()
.then(asyncThing2)
.then(asyncThing3)
.catch(asyncRecovery1)
.then(asyncThing4, asyncRecovery2)
.catch(err => { console.log("Don't worry about it") })
.then(() => { console.log("All done!") })

에러 핸들링

16-2-6. Multi Handling

1. Promise.all()

  • iterable의 모든 요소가 fulfilled되는 경우: 전체 결과값들을 배열 형태로 then에 전달.
  • iterable의 요소 중 일부가 rejected되는 경우: 가장 먼저 rejected 되는 요소 '하나'의 결과를 catch에 전달.
const arr = [
	1,
	new Promise((resolve, reject) => {
		setTimeout(()=> {
			resolve('resolved after 1000ms')
		}, 1000)
	}),
	'abc',
	() => 'not called function',
	(() => 'IIFE')()
]

Promise.all(arr)
.then(res => { console.log(res) })
.catch(err => { console.error(err) })
const arr = [
	1,
	new Promise((resolve, reject) => {
		setTimeout(()=> {
			reject('rejected after 1000ms')
		}, 1000)
	}),
	'abc',
	()=> 'not called function',
	(()=> 'IIFE')()
]

Promise.all(arr)
.then(res => { console.log(res) })
.catch(err => { console.error(err) })

2. Promise.race()

  • iterable의 요소 중 가장 먼저 fulfilled / rejected되는 요소의 결과를 then / catch에 전달.
const arr = [
	new Promise(resolve => {
		setTimeout(()=> { resolve('1번요소, 1000ms') }, 1000)
	}),
	new Promise(resolve => {
		setTimeout(()=> { resolve('2번요소, 500ms') }, 500)
	}),
	new Promise(resolve => {
		setTimeout(()=> { resolve('3번요소, 750ms') }, 750)
	})
]
Promise.race(arr)
.then(res => { console.log(res) })
.catch(err => { console.error(err) })
const arr = [
	new Promise(resolve => {
		setTimeout(()=> { resolve('1번요소, 0ms') }, 0)
	}),
	'no queue'
]
Promise.race(arr)
.then(res => { console.log(res) })
.catch(err => { console.error(err) })

참고: ES2017 Async Function