Callback Hell
아래 코드는 ajax를 이용한 비동기 요청 처리입니다.
버튼 클릭 => 서버에 users 리스트 요청 => 성공 시 list의 첫 번째 user의 정보 요청 => 성공 시 user의 img url을 사용하여 img 요소 추가 => img 클릭 시 img 삭제하는 간단한 코드이다.
콜백 함수가 나올 때마다 코드의 깊이가 깊어지고 있다.
const script = document.createElement('script')
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js'
document.body.appendChild(script);
document.body.innerHTML += `<button id="btn">클릭</button>`
document.getElementById('btn').addEventListener('click', () => {
$.ajax({
method: 'GET',
url: 'https://api.github.com/users?since=1000',
success: (data) => {
const target = data[2]
$.ajax({
method: 'GET',
url: `https://api.github.com/user/${target.id}`,
success: (data) => {
const _id = 'img_' + data.id
document.body.innerHTML = `<img id=${_id} src=${data.avatar_url} />`
document.getElementById(_id).addEventListener('click', (e) => {
e.target.remove()
})
},
error: (err) => {
console.log(err)
}
})
},
error: (err) => {
console.log(err)
}
})
})
Promise 사용
Promise 문법을 사용하면 ajax를 사용했을 때 보다 적은 depth.
document.body.innerHTML = `<button id="btn">클릭</button>`
document.getElementById('btn').addEventListener('click', () => {
fetch('https://api.github.com/users?since=1000') // html5 API
.then(res => res.json()) // data 형식을 json으로 변환
.then(res => {
const target = res[0]
return fetch(`https://api.github.com/user/${target.id}`)
})
.then(res => res.json())
.then(res => {
const _id = `img_${res.id}`
document.body.innerHTML = `<img id=${_id} src=${res.avatar_url} />`
document.getElementById(_id).addEventListener('click', (e) => {
e.target.remove()
})
}).catch(err => {
console.log(err);
})
})
Axios: Promise를 반환하면서 Json parsing을 자동으로 해주는 라이브러리
.then(res => res.json())
부분을 자동으로 해줌.
const script = document.createElement('script')
script.src = `https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js`
document.body.appendChild(script);
document.body.innerHTML = `<button id="btn">클릭</button>`
document.getElementById('btn').addEventListener('click', () => {
axios.get('https://api.github.com/users?since=1000') // html5 api .then(res => res.json()) // data 형식을 json으로 변환
.then(res => {
const target = res.data[0]
return axios.get(`https://api.github.com/user/${target.id}`)
})
.then(res => {
const _id = `img_${res.data.id}`
document.body.innerHTML = `<img id=${_id} src=${res.data.avatar_url} />`
document.getElementById(_id).addEventListener('click', (e) => {
e.target.remove()
})
}).catch(err => {
console.log(err);
})
})
Promise의 구조를 살펴보면 prototype에 then, catch, finally 등 메소드가 있습니다.
이러한 메소드들은 Promise를 생성자(클래스)로 만든 인스턴스에서 접근이 가능하며
all 메소드는 static 메소드르 Promise에서 바로 접근이 가능하다.
unnsettled(미확정) 상태: pending, thenable하지 않다. 비동기 처리가 끝나기 전 단계
settled(확정) 상태 : resolved, thenable한 상태.
ex) 브라우저마다 사용 단어가 다를 수 있음.
const promiseTest = (param, delay) => new Promise((resolve, reject) => {
// resolve: 성공 시 함수, reject: 실패 시 함수
setTimeout(() => {
if(param){
resolve('성공')
}else{
reject('실패')
}
}, delay);
})
const testRun = (param, delay) => promiseTest(param, delay)
.then(res => console.log(res))
.catch(err => console.log(err))
const a = testRun(true, 2000) // 성공
const b = testRun(false, 3000) // 실패
new Promise((resolve, reject) => {...})
.then(res => { ... })
.catch(err => { ... })
const simplePromise = value => {
return new Promise((resolve, reject) => {
if(value) { resolve(value) }
else { reject(value) }
})
.then(res => console.log(res))
.catch(err => console.log(err))
}
simplePromise(1) // 1
simplePromise(0) // 0
// then, catch는 promise를 반환하기 때문에 then을 이어 사용이 가능하다.
const a = simplePromise(1)
a.then(res => {console.log('then 이어 쓰기')})
.then(res => console.log('then 더 이어 쓰기'))
.then(res => console.log('then 더더 이어 쓰기'))
// 전체적인 함수를 실행하고 결과를 반환
new Promise((resolve, reject) => {
// 내용 ~~~
resolve(42)
}).then(res => console.log(res)) // 42
// 처음부터 식이나 값을 바로 반환, 내용은 올 수 없음.
Promise.resolve(10)
.then(res => console.log(res)) // 10
.catch(err => console.log(err))
const a = val => Promise.resolve(val)
.then(res => console.log(res));
a(10) // 10
Promise.reject도 마찬가지로 식이나 값을 바로 반환
Promise.reject(20)
.then(res => {console.log(res)})
.catch(err => {console.error(err)}) // 20
thenable 객체가 then 메소드를 가지고 그 메소드가 resolve, reject 함수를 호출 할 수 있게 구현이 되어있는 객체라면 이 객체는 thenable하다. (Duck typing)
const thenable = {
then( resolve, reject) {
resolve(10)
}
}
const a = Promise.resolve(thenable) // 10 , resolved
const b = Promise.resolve(20) // 20 , resolved
const thenable2 = {
then( resolve, reject) {
reject(10)
}
}
const c = Promise.resolve(thenable) // 10 , rejected
.then, .catch 에서
1. return promise 인스턴스: pomise 인스턴스가 리턴됨 // return new Promise() or return Promise.resolve()
2. return 일반값: promise 객체에 resolved 상태로 반환. 그 안에 값이 담김. // Promise {<<resolved: 값>>}
3. return 안하면 : return undefined
4. Promise.resovle() or Promise.reject(): return 해주지 않으면 의미가 없음.
// 별개의 Promise 객체가 생성될뿐, 현재 process상의 Promise 플로우에 영향을 주지 않음.
// return 해준다면 1번과 같음.
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('첫번째 프로미스')
}, 1000);
})
.then(res => {
console.log(res) // 첫번째 프로미스
return '두번째 프로미스' // return 값은 resolve의 결과로 나옴.
// 즉 다음 then에서 res 값
})
.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 => { // reject이므로 pass
console.log(res)
})
.catch(err =>{
console.error(err) // 네번째 프로미스
return new Error('이 에러는 then에 잡힘') // 일반값 반환, thenable한 상태
})
.then(res => {
console.log(res) // 이 에러는 then에 잡힘
throw new Error('이 에러는 catch에 잡힘')
})
.then(res => {
console.log('출력 안됨')
})
.catch(err => {
console.error(err) // 이 에러는 catch에 잡힘
})
// 첫번째 프로미스
// 두번째 프로미스
// 세번째 프로미스
// 네번째 프로미스
// Error: 이 에러는 then에 잡힘 at <anonymous>:32:16
// Error: 이 에러는 catch에 잡힘 at <anonymous>:36:15
promise.all(iterable)
all or one error
Array.prototype.every()와 비슷: 배열 내 모든 값이 true일 때 true 반환
일반값은 그냥 resolved 됨.
iterable의 모든 요소가 fullfilled 되는 경우: 전체 결과값들을 배열 형태로 then에 전달.
iterable의 요소 중 일부가 rejected 되는 경우: 가장 먼저 rejected 되는 요소 하나를 catch에 전달.
const arr = [
1,
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolved after 1s')
}, 1000);
}),
'123',
() => 'not called function',
(()=> 'IIFE') // 즉시 실행 함수
]
Promise.all(arr)
.then(res => console.log(res))
.catch(err => console.error(err))
// [1, "resolved after 1s", "123", ƒ, ƒ]
const arr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolved after 1s')
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject('resolved after 2s')
}, 2000);
}),
'출력 될까???'
]
Promise.all(arr)
.then(res => console.log(res))
.catch(err => console.error(err))
// resolved after 2s
Promise.reac(iterable)
only the fastest one
Array.prototype.some()과 비슷: 배열의 요소 중 하나의 값이 true이면 true 반환
일반값은 그냥 resolved된 값으로 간주.
iterable 내의 요소 중에 가장 먼저 resolved(then), rejected(catch) 된 값을 반환.
resolved, rejected 상관없이 가장 빠른 값이 반환된다.
const arr = [
new Promise(resolve => {
setTimeout(() => {
resolve('1번 요소, 1s')
}, 1000);
}),
new Promise(resolve => {
setTimeout(() => {
resolve('2번 요소, 0.5s')
}, 500);
}),
new Promise(resolve => {
setTimeout(() => {
resolve('3번 요소, 0.1s')
}, 100);
}),
]
Promise.race(arr)
.then(res => console.log(res))
.catch(err => console.error(err))
// 3번 요소, 0.1s
Promise.race()를 사용하면 setTimout을 0s로 설정한 것과 일반 값 중 어떤 값이 출력이 될까?
기본 값이 출력된다. setTimout 함수를 사용하면 이벤트 큐에 들어가므로 사실 상 0s는 아님.
const arr =[
new Promise(resolve => {
setTimeout(() => {
resolve('1번, 0s')
}, 0);
}),
'no queue'
]
Promise.race(arr)
.then(res => console.log(res))
.catch(err => console.error(err))
// no queue
서버 응답 값을 then으로 받을 필요가 없음.
const script = document.createElement('script')
script.src = `https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js`
document.body.appendChild(script);
document.body.innerHTML = `<button id="btn">클릭</button>`
document.getElementById('btn').addEventListener('click', async () => {
try{
const res = await axios.get('https://api.github.com/users?since=1000')
const user = res.data[0]
const _id = `img_${user.id}`
document.body.innerHTML = `<img id=${_id} src=${user.avatar_url} />`
document.getElementById(_id).addEventListener('click', (e) => {
e.target.remove()
})
}catch(err){
console.log(err)
}
})