동시동작이 자바스크립트는 한 스레드에서 실행된다. 한 함수가 실행되면 다른 함수는 끝날때까지 기다려야 한다. 따라서, 상호배제 mutex, 교착상태 deadlock 등의 걱정은 필요없다.
오래걸리는 작업은 항상 비동기 asynchronous 로 처리한다. 수행할 작업을 지정하고, 데이타를 이용할수 있거나 오류가 발생했을때 실행한 콜백함수를 제공한다. 그 동안 현재함수는 다음 코드를 이어서 실행한다.
프라미스를 만드는 방법을 살펴볼테지만, 실제로는 대부분의 라이브러리가 프라미스를 반환해주기 때문에 직접 프라미스를 만들 필요는 거의 없다.
const produceAfterDaly = (result, delay) => {
return new Promise((resolve, reject) => {
const callback = () => resolve(result)
setTimeout(callback, delay)
})
}
결과가 이미지인 프라미스를 반환하는 함수
const loadImage = url => {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
const callback = () => {
if (request.status === 200) {
const blob = new Blob([request.response], {type: 'image/png'})
const img = document.createElment('img')
img.src = URL.createObjectURL(blob)
resolve(img)
} else {
reject(Error(`${request.status}: ${request.statusText}`))
}
}
request.open('GET', url)
request.responseType = 'blob'
request.addEventListener('load', callback)
request.addEventListener('error', (event) =>
reject(Error('Network error'))
);
request.send()
})
}
document.addEventListener('DOMContentLoaded', () => {
const imgdiv = document.getElementById('images')
const promise = loadImage('../../hanafuda/1-1.png')
console.log({promise})
promise.then(img => {
imgdiv.appendChild(img)
console.log({promise})
})
})
Promise.resolve(value)를 호출하면 주어진 값으로 프라미스 실행이 바로 완료된다.
const loadImage = url => {
if (url === undefined ) return Promise.resolve(brokenImage)
...
}
프라미스가 완료될때까지 기다릴 필요가 없으며 프라미스가 완료될때 결과 또는 오류를 처리할 액션을 제공한다. 이 들 액션을 추가함수가 끝나면 액션함수가 실행된다.
then 메서드로 액션을 지정하면 프라미스가 해석된 다음 액션이 실행된다.
const promise1 = loadImage('../../hanafuda/1-1.png')
promise1.then(img => imgDiv.append(img))
{
const imgdiv = document.getElementById('images3')
loadImage('../../hanafuda/3-1.png')
.then(img => imgdiv.appendChild(img)) // Synchronous
.then(() => loadImage('../../hanafuda/3-2.png')) // Asynchronous
.then(img => imgdiv.appendChild(img)) // Synchronous
}
// Caution: The then method discards non-function arguments
// The last image is loaded but not appended
{
const imgdiv = document.getElementById('images6')
loadImage('../../hanafuda/6-1.png')
.then(img => imgdiv.appendChild(img))
.then(loadImage('../../hanafuda/6-2.png'))
// Error—argument of then isn't a function
.then(img => imgdiv.appendChild(img))
}
})
// The finally method
{
const imgdiv = document.getElementById('images7')
const doCleanup = text => imgdiv.appendChild(document.createTextNode(text))
const url = '../../hanafuda/1-1.png'
Promise.resolve()
.then(() => loadImage(url))
.then(img => imgdiv.appendChild(img))
.finally(() => { doCleanup('All done. ') })
.catch(reason => console.log({reason}))
}
프라미스를 이터러블에 저장한후 Promise.all(iterable) 을 호출한다. 그러면 그 결과가 프라미스 순서대로 이터러블에 저장된다. Promise.all 은 모든 태스크를 병렬로 실행하지 않으며 한 스레드에서 순차적으로 실행된다. 하지만 작업순서는 예상할수 없어 어떤 프라미스결과가 먼저일지 알수 없다.
const promises = [
loadImage('../../hanafuda/1-1.png'),
loadImage('../../hanafuda/1-2.png'),
loadImage('../../hanafuda/1-3.png'),
loadImage('../../hanafuda/1-4.png')
]
Promise.all(promises)
.then(images => { for (const img of images) imgdiv.appendChild(img) })
거절된 결과를 좀 더 적절하게 처리하기 위해선 Promise.allSettled 메서드를 사용한다.
Promise.allSettled(promises)
.then(results => { console.log(results) })
// [{status: 'fulfilled', value: img}, {status: 'fulfilled', value: img}, {status: 'rejected', reason: Error}, {status: 'fulfilled', value: img}]
처음으로 완료된 프라미스가 결과를 좌우한다. Promise.race(iterable)은 한 프라미스의 결과가 완료될 때가지 프라미스들을 실행한다.
Promise.race(promises)
.then(img => imgdiv.appendChild(img))
async 키워드를 갖는 함수에서만 await 를 사용할 수 있는데, 프라미스 동작이 완료된후 await 이후의 동작이 실행되도록 한다.
const putImage = async (url, element) => {
const image = await loadImage(url)
element.appendChild(img)
}
// =
const putImage = (url, element) => {
loadImage(url)
.then(img => element.appendChild(img))
}
await 는 여러번 사용할수 있고 루프도 사용할수 있다.
const putTwoImages = async (url1, url2, element) => {
const img1 = await loadImage(url1)
element.appendChild(img1)
const img2 = await loadImage(url2)
element.appendChild(img2)
}
const putImages = async (urls, element) => {
for (const url of urls) {
const img = await loadImage(url)
element.appendChild(img)
}
}
async 함수는 다음 상황에 적용할수 있다
// 화살표 함수
async url => { . . . }
async (url, params) => { . . . }
// 메서드
class ImageLoader {
async load(url) { . . . }
}
// 명명된 함수와 익명함수
async function loadImage(url) { . . . }
async function(url) { . . . }
// 객체 리터럴 메서드
obj = {
async loadImage(url) { . . . },
. . .
}
async 함수는 프라미스를 반환한다.
const result = await fetch('https://aws.random.cat/meow')
const imageJSON = await result.json()
Fetch API 에서 JSON 처리는 비동기이므로 두번째 await 는 반드시 사용해야 한다.
const getCatImageUrl = async () => {
const result = await fetch('https://aws.random.cat/meow')
const imageJson = await result.json()
return await loadImage(imageJson.file)
}
async 함수가 await 를 만나기전에 값을 반환하면 Promise.resolve 로 감싼다.
// An async function returning a value (actually a resolved promise)
const getJSONProperty = async (url, ...keys) => {
if (url === undefined) return null
// Actually returns Promise.resolve(null)
const result = await fetch(url)
const json = await result.json()
let value = json
for (const key of keys) value = value[key]
return value
}
await 를 연속으로 사용하면 순서대로 실행된다. 동시에 실행하게 하려면 Promise.all 을 활용한다.
const [img1, img2] = await Promise.all([loadImage(url), loadCatImage()])
async 함수에서 예외를 던지면 Promise.reject 를 반환한다.
반면 await 연산자가 거절된 프라미스를 받으면 예외를 일으킨다.
const getAnimalImageURL = async type => {
if (type === 'cat') {
return getJSONProperty('https://aws.random.cat/meow', 'file')
} else if (type === 'dog') {
return getJSONProperty('https://dog.ceo/api/breeds/image/random', 'message')
} else {
throw Error('bad type') // Async function returns rejected promise
}
}
const getAnimalImage = async type => {
try {
const url = await getAnimalImageURL(type)
return loadImage(url)
} catch {
return brokenImage
}
}
로딩된 순서가 아닌 올바른 순서대로 이미지 배치
const loadImage = url => {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
const callback = () => {
if (request.status == 200) {
const blob = new Blob([request.response], {type: 'image/png'})
const img = document.createElement('img')
img.src = URL.createObjectURL(blob)
resolve(img)
} else {
reject(Error(`${request.status}: ${request.statusText}`))
}
}
request.open('GET', url)
request.responseType = 'blob'
request.addEventListener('load', callback)
request.addEventListener('error', event => reject(Error('Network error')));
request.send()
})
}
document.addEventListener('DOMContentLoaded', () => {
const imgdiv = document.getElementById('images')
let promises = [loadImage('../../hanafuda/1-1.png'), loadImage('../../hanafuda/1-2.png'), loadImage('../../hanafuda/1-3.png'), loadImage('../../hanafuda/1-41.png')]
Promise.all(promises).then( (images) => {
for (const img of images) imgdiv.appendChild(img)
})
})