L&D Oct. 2020
Steve Olsen
GOALS:
What?... Why?
Async code sucked...
const delayCallback = (ms, cb) => {
console.log('Delayed Callback', {ms})
setTimeout(cb, ms)
}
const addCallback = (a, b, cb) => {
const result = a + b
// This is SYNCHRONOUS style:
// return result
// But we want ASYNCHRONOUS style:
// cb(result)
// ...With a delay:
delayCallback(100,
() => cb(result)
)
}
addCallback(1, 2,
(result1) => addCallback(result1, result1,
(result2) => console.log('callback:', result2)
// ....
// ....
)
)
*Smart people promised us
Promise: a container for a value, accessed via a callback
("monad" or "monoid" in FP)
A Promise has state:
pending
resolved OR rejected
const myPromise = new Promise(
(resolve, reject) => {
resolve(42)
}
)
myPromise
.then(
(value) => {
console.log(value)
return value + 1 // Tip: usually want to return a chained value!
}
)
.then((value2) => console.log(value2))
const delay = (ms) => new Promise(resolve => {
setTimeout(() => resolve(), ms)
})
const addAsync = (a, b) => {
return new Promise((resolve, reject) => { // create our own Promise
const result = a + b
delay(1000)
.then(() => resolve(result))
})
}
addAsync(10, 20)
.then(result1 => addAsync(result1, result1))
.then(result2 => console.log(result2))
// Other promise helpers/shortcuts
Promise.resolve(1)
Promise.reject(new Error())
Promise.all([promise1, promise2])Pesky Errors...
try {
(null).prop
} catch (error) {
console.error(error)
}
try {
throw new Error('my custom error')
} catch (error) {
console.error(error)
}
const addCallback = (a, b, cb) => {
const result = a + b
// ...oops!
throw new Error('bad calulator CPU! 1')
}
try {
addCallback(1, 2, console.log)
} catch (error) {
console.error(error)
}
const add = (a, b) => {
return new Promise((resolve, reject) => {
const result = a + b
// ...oops!
reject(new Error('bad calulator board 2'))
})
}
add(10, 20)
.then(r => console.log(r))
.catch(error => console.error(error))
.then(() => console.log('carry on!'))
// finally {....}
// .finally(() => {....})const value = 10
const finalValue = Promise.resolve(value)
.then(val1 => val + 10)
.then(val2 => {
console.log(val2)
})
.then(val2 => { val2 - 5 })
console.log(finalValue)const value = 10
const finalValue = Promise.resolve(value)
.then(val1 => val + 10)
.then(val2 => {
Promise.reject(new Error("no way! " + val2))
})
.catch(error => console.log(error.message))
.then(() => { value }) // set to default value
console.log(finalValue)Await there, just a minute you!
async function main() {
...
}
main()const add = async (a, b) => {
return new Promise(
async (resolve, reject) => { // create our own Promise
const result = a + b
await delay(100) // like "sleep"
resolve(result)
}
)
}
async function main() {
const r1 = await add(40, 40)
const r2 = await add(r1, r1)
console.log('async/await:', r1, r2)
}
main()async function work() {
let result
try {
result = await add(1, 2) // don't forget the await !!
} catch (error) {
console.error(error)
}
console.log('carry on! 5', result)
}
work()Promise.then / Promise.catch
async function work3() {
let result
try {
result = await add(1, 2)
.catch(
() => "Add Error converted into a String!"
)
} catch (error) {
console.error('try/catch:', error) // never runs!
}
console.log('carry on!', result)
}
work3()Promise.then / Promise.catch
async function work2() {
let result
try {
result = await add(1, 2)
.catch((error) => {
console.error('.catch', error)
return Promise.reject(error)
})
} catch (error) {
console.error('try/catch:', error)
}
console.log('carry on!', result)
}
work2()const myFunc = async () => {
const value = Promise.resolve(10)
const value2 = await (value + 10)
if (value > 10) {
Promise.reject(new Error("too big!"))
}
}
try {
myFunc()
} catch (err) {
console.error("I want to catch an error here!", err.message)
}// We've seen autoboxing already:
myVar = Promise.resolve(1).then(v => v + 1)
// Q: result is 2, or Promise<2>?
myVar = Promise.resolve(1).then(v => Promise.resolve(v + 1)) // same
myVar = await 4
// Q: myVar1 is 4, or Promise<4>?
myVar = await Promise.resolve(4) // same
myVar = Promise.resolve(Promise.resolve(1))
// Q: myVar is Promise<1>, or Promise<Promise<1>>?
myVar = await Promise.resolve(Promise.resolve(1))
// Q: myVar is 1, or Promise<1>?
// not so for Promise.reject(Promise.reject(...)) - don't mess with reject!
myVar = await Promise.resolve(Promise.reject(new Error('message')))
throw new Error('message') // same
Can't we all just get along!
| Your API: callback | Your API: Promise | |
|---|---|---|
| Their API: callback | #A | #B |
| Their API: Promise | #C | #D |
// MyCallback + Callback
function getData(id, cb) {
api('/user/' + id,
function(err, data) {
if (err) cb(err);
else cb(null, data);
}
);
}// MyPromise + Callback
async function getData(id) {
return new Promise(function(resolve, reject) {
api('/user/' + id,
function(err, data) {
if (err) reject(err);
else resolve(data);
}
);
});
}// MyCallback + Promise
function getData(id, cb) {
api('/user/' + id)
.then(function (data) { cb(null, data) })
.catch(function (err) { cb(err) });
}// MyPromise + Promise
async function getData(id) {
return api('/user/' + id);
}Are these threads, or not?!
Single-Core Threading!
("You don't have to await every Promise")
async function getArticleDate(id) {
const data = await callDatabase(id)
const time = new Date(data.timestamp)
return time
}function randomNumberReturn() {
if (Math.random() < 0.5) {
return 42
} else if (Math.random() >= 0.5) {
return Promise.resolve(42)
}
// could return undefined!!
}
async function work() {
const data = await randomNumberReturn() // might be a promise
return data * 2
}async function sendTelemetry(eventName, eventValue) {
const config = await getRemoteSecret('GoogleAnalytics')
const key = config.key
sendSecureTelemetryEvent(key, eventName, eventValue)
.catch((err) => console.log("This is bad, I can't do much about it!"))
}
async function getArticleDate(id) {
const data = await callDatabase(id)
const time = new Date(data.timestamp)
sendTelemetry(id + "_time", time) // don't await this thread!
return time
}note: It can't throw an Error
Doesn't mean you have to wait for it.
so other threads don't starve.
Turn it up to 11
Thread 1 Thread 2
Priority!
(Promises)
Normal
(Timers/IO)
new Promise(async (res) => {
console.log('in promise callback 1');
await 1; // this seems unimportant.... is it??
console.log('in promise callback 2');
})
console.log('after Promise instantiated');
setTimeout(() => { console.log('TIMER DONE!', v) }, 1)
const p1 = Promise.resolve(1)
p1
.then((v) => v)
.then((v) => v)
.then((v) => { console.log('PROMISE1 DONE!', v) })
const p2 = Promise.resolve(2)
p2
.then((v) => v)
.then((v) => { console.log('PROMISE2 DONE!', v) })let shared = ''
function threadA() {
new Promise(async res => {
shared = 'a'
shared = await Promise.resolve('a')
shared = await 'a'
await Promise.resolve()
console.log('a ??', shared)
})
}
function threadB() {
new Promise(async res => {
shared = 'b'
shared = await Promise.resolve('b')
shared = await 'b'
await Promise.resolve()
console.log('b ??', shared)
})
}
threadA()
threadB()const t1 = () => Promise.resolve(1) // eg. fetch()
const t2 = async () => 2 // eg. fetch()
const t3 = async () => 3 // eg. fetch()
// parallel A
const allResults = await Promise.all(
[t1(), t2(), t3()]
)
// parallel B
const workQueue = [t1, t2, t3]
const allResults = await Promise.all(
workQueue.map(t => t())
)
// synchronous A
for (let t of [t1, t2, t3]) {
const result = await t()
}
// synchronous B
// can't use .forEach(),
// beacuse you can't get the previous Promise to await a resolve
[t1, t2, t3].reduce(async (prevPromise, t) => {
const prevResult = await prevPromise
// handle result here
return t()
}, Promise.resolve())
We've been Tweet Rushed...
Dictionary <key: Promise>
const promiseCache = {}
async function getCachedData(key) {
if(!promiseCache[key]) {
promiseCache[key] = new Promise((resolve, reject) => {
getDataFromDatasource(key) // request goes to DB, returns Promise
.then((responseData) => { resolve(responseData) })
.catch((error) => { reject(error) })
.finally(async () => {
await delay(1000)
delete promiseCache[key]
});
})
}
return promiseCache[key]
}
async function getCachedData2(key) {
if(!promiseCache[key]) {
promiseCache[key] = getDataFromDatasource(key)
.finally(async () => {
await delay(1000)
delete promiseCache[key]
});
}
}
return promiseCache[key]
}>>>Dictionary/Object collection
>>>Queue/Array collection + delay
fetch(url, opts)
.then(response => response.json())
.then(result => { ... })
(Precursor to Async/Await)
(Come and learn, later on...)
function* fourNumbers() {
let count = 0
while (count < 3) {
yield count
count += 1
}
return 99
}
const myGenerator = fourNumbers()
console.log(myGenerator.next()) // 1
console.log(myGenerator.next()) // 2
console.log(myGenerator.next()) // 3
console.log(myGenerator.next()) // 4
console.log(myGenerator.next()) // 5
/*
{value: 0, done: false}
{value: 1, done: false}
{value: 2, done: false}
{value: 99, done: true}
{value: undefined, done: true}
*/async function* fourNumbers() {
let count = 1
while (count < 4) {
yield count
count += 1
}
return 99
}
const myGenerator = fourNumbers()
console.log(await myGenerator.next()) // 1
console.log(myGenerator.next()) // 2
console.log(myGenerator.next()) // 3
console.log(myGenerator.next()) // 4
console.log(myGenerator.next()) // 5
/*
{value: 1, done: false}
Promise<{value: 2, done: false}>
Promise<{value: 3, done: false}>
Promise<{value: 99, done: true}>
Promise<{value: undefined, done: true}>
*/(Arguments from a template literal)
(Come and learn, later on...)
// TYPICAL FUNCTION:
function greet(...values){};
// TAG FUNCTION DEFINITION
function greet([tokens, ...values]){
console.log(tokens); // ["I'm ", ". I'm ", " years old."]
console.log(values); // [name, age]
};
// TAG FUNCTION CALL:
greet`I am ${name}. I am ${age} years old.`;
// EQUIVALENT FUNCTION CALL:
greet(["I'm ", ". I'm ", " years old."], name, age);
Promises are Powerful.
Promises are Simple.
Promises are Fun.
I promise.
Questions?