new Promise((resolve, reject) => {})
const getDinosaur = (name) => {
return new Promise((resolve, reject) => {
resolve({name})
})
}
//Shorthand for synchronous values as a Promise
const getDinosaur = (name) => Promise.resolve({name})
const getDinosaur = (name) => Promise.reject(new Error("Dinosaurs went extinct!")
- Get the asynchronous value
getDinosaur("Stegosaurus")
.then((dinosaur) => console.log(dinosaur))
.then() coerces all returned values to a Promise
getDinosaur("Brontosaurus")
.then((dinosaur) => dinosaur.name)
.then((name) => console.log(name))
- Handle errors with a
Promise
const getDinosaurs = () => Promise.reject("Connection Timed Out!")
getDinosaurs()
.catch((reason) => console.error(reason))
const getDinosaur = (name) =>
new Promise((resolve) =>
setTimeout(() =>
resolve({name, age: jurrasicPeriod()}),
Math.random() * 3000)
)
const jurrasicPeriod = () => Math.floor(Math.random() * 54e6) + 145e6
const log = (value) => console.log(value)
const dinosaurs = ['Brontosaurus', 'Tyrannosaurus', 'Stegosaurus']
Promise.all(dinosaurs.map(getDinosaur))
.then(log) //-> [{name: "Brontosaurus", age: ...}, {name: "Tyrannosaurus", age: ...}, {name: "Stegosaurus", age: ...}]
- Get the full stack-trace you expect from a
Promise chain link
- A pass-through that gives access to the current value
//Warning: Modifies the Promise prototype
Promise.prototype.tap = function (onFulfilled, onRejected) {
return this.then(
(result) => {
onFulfilled
? Promise.resolve(onFulfilled(result))
.then(() => result)
: Promise.resolve(result)
},
(reason) => {
onRejected
? Promise.resolve(onRejected(reason))
.then(() => Promise.reject(reason))
: Promise.reject(reason)
}
)
}
const log = (value) => console.log(value)
const logError = (reason) => console.error(reason)
Promise.resolve('Roar!').tap(log)
Promise.reject(new Error('Timed Out')).tap(log, logError)
takeALongTimeToLoad().tap(log).always(hideLoadingIndicator)
- Do something on settled. (fulfilled or rejected)
//Warning: Modifies the Promise prototype
Promise.prototype.always = function (onSettled) {
return this.then(
(value) => Promise.resolve(onSettled(value)).then(() => value),
(reason) => Promise.resolve(onSettled(reason)).then(() => value))
}
const yell = (words) => console.log(`${words.toUpperCase()}!!!`)
const log = (value) => console.log(value)
Promise.resolve('roar').always(log).then(yell)
Promise.reject(new Error('Timed Out')).always(log)
takeALongTimeToLoad().catch(renderError).always(hideLoadingIndicator)
- Improves readability and debugging
const getName = (dinosaur) => dinosaur.name
const log = (value) => console.log(value)
getDinosaur()
.then(getName)
.then(log)
- Insert a different value if a
Promise fails.
const getData = () => Promise.reject(new Error('Connection Timed Out'))
const getCachedData = () => Promise.resolve({name: 'Brontosaurus'})
const log = (value) => console.log(value)
getData()
.catch(getCachedData) //Could get data from localStorage
.then(log)
- Can be used any time an external resource isn't reliable
- Taking a
Promise and independently calling multiple .then() on it
const dinosaurs = getDinosaurs() //Returns a promise
dinosaurs
.then(cacheDinosaurs)
dinosaurs
.then(renderDinosaurList)
dinosaurs
.then(renderDinosaursCount)
// This is how JQuery's $.ready() works, but with callbacks
const domReady = () => {
const readyState = document.readyState
return readyState === "interactive" || readyState === "complete"
? Promise.resolve()
: new Promise((resolve) =>
document.addEventListener("DOMContentLoaded", resolve))
}
const roar = () => console.log("Roar!!!")
const attachRoarHandler = () =>
document.querySelector("button").addEventListener("click", roar);
domReady() // Promise only resolves when the DOM is loaded
.then(attachRoarHandler)
- Can be coupled with fanning to great effect
- Store a
Promise into a cache instead of the values
const cache = {}
const getDinosaur = (name) =>
cache[name]
? cache[name]
: cache[name] = Promise.resolve({name})
const log = (value) => console.log(value)
getDinosaur('Stegosaurus')
.then(log)
getDinosaur('Stegosaurus')
.then(log)
console.log(getDinosaur('Stegosaurus') === getDinosaur('Stegosaurus')) //-> true
- This way, a hundred different things can request a remote resource and it will only hit it once
- It does this by storing an unfulfilled
Promise
- Synchronous values don't populate the cache until the request finished causing all of the requests that come in before the request resolves will also try and hit the external resource
- Return the same
Promise when something is asked for until it resolves
let throttledPromise
const resetThrottle = () => throttledPromise = undefined
const getDinosaurs = () =>
throttledPromise
? throttledPromise
: throttledPromise = Promise.resolve({name}).tap(resetThrottle, resetThrottle)
const dinosaurs = getDinosaurs()
console.log(dinosaurs === getDinosaurs()) //-> true
setTimeout(() => {
//By now, the Promise has settled and is no longer pending
console.log(dinosaurs === getDinosaurs()) //-> false
})
- This keeps data fresh, but prevents parallel requests for the same thing
- All parallel requests get the same
Promise back until it resolves and a new Promise is made
Promise.race offers a method to put a time-limit on how long an asynchronous task can take
const retrieveDinosaurs = () =>
new Promise((resolve) => setTimeout(() => resolve(["Brontosaurus"]), 5000))
const saveDinosaursToCache = (dinosaurs) =>
localStorage.dinosaurs = JSON.stringify(dinosaurs)
const getDinosaursFromCache = () =>
JSON.parse(localStorage.dinosaurs || '[]')
const getDinosaurs = () =>
retrieveDinosaurs().tap(saveDinosaursToCache)
const getCachedDinosaurs = () =>
new Promise((resolve, reject) =>
setTimeout(
() => resolve(getDinosaursFromCache()),
1000 //Don't resolve until 5 seconds have passed
)
)
const log = (value) => console.log(value)
Promise.race([getDinosaurs(), getCachedDinosaurs()])
.then(log)
- If a connection is unreliable, take data from the cache and update the cache later
- The future syntax of
Promise
async function getDinosaurs () {
const dinosaurs = await api.dinosaurs.get() //returns a Promise
const names = dinosaurs.map((dinosaur) => dinosaur.name)
return names
}