Skip to content

Instantly share code, notes, and snippets.

@FauxFaux
Created December 18, 2018 10:58
Show Gist options
  • Save FauxFaux/8880b9bf4015eb6dad9fbe37a52b0a21 to your computer and use it in GitHub Desktop.
Save FauxFaux/8880b9bf4015eb6dad9fbe37a52b0a21 to your computer and use it in GitHub Desktop.
Type system can't save you from missing awaits
/*
% npm install typescript
.. warnings blah blah..
+ typescript@3.2.2
updated 1 package and audited 2 packages in 0.538s
found 0 vulnerabilities
% node_modules/.bin/tsc --lib es2018,dom --target es2018 --strict --noImplicitReturns --noImplicitAny --out hello.js hello.ts
% node hello.js
buggy1: .- startup
buggy1: | β†’ lock
buggy1: | ← unlock
buggy1: | πŸ‘“ inside
buggy1: '- done
buggy2: .- startup
buggy2: | β†’ lock
buggy2: | ← unlock
buggy2: | πŸ‘“ inside
buggy2: '- done
buggy3: .- startup
buggy3: | πŸ‘“ inside
buggy3: | β†’ lock
buggy3: | ← unlock
buggy3: '- done
buggy4: .- startup
buggy4: | β†’ lock
buggy4: | πŸ‘“ inside
buggy4: '- done
buggy4: | ← unlock
buggy5: .- startup
buggy5: | πŸ‘“ inside
buggy5: '- done
buggy5: | β†’ lock
buggy5: | ← unlock
buggy6: .- startup
buggy6: | ← unlock
buggy6: | πŸ‘“ inside
buggy6: '- done
buggy6: | β†’ lock
okay1: .- startup
okay1: | β†’ lock
okay1: | πŸ‘“ inside
okay1: | ← unlock
okay1: '- done
okay2: .- startup
okay2: | β†’ lock
okay2: | πŸ‘“ inside
okay2: | ← unlock
okay2: '- done
app complete
*/
class Lock {
name: string;
constructor(name: string) {
this.name = name;
}
async lock(): Promise<void> {
await delay(30);
console.log(`${this.name}: | β†’ lock`);
}
async unlock(): Promise<void> {
await delay(10);
console.log(`${this.name}: | ← unlock`);
}
}
async function buggy1<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
await l.lock();
try {
// no await, resolved in parent
return cb();
} finally {
await l.unlock();
}
}
async function buggy2<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
await l.lock();
try {
// still no await, explicit resolve does nothing
return Promise.resolve(cb());
} finally {
await l.unlock();
}
}
async function buggy3<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
// no waiting for lock
l.lock();
try {
return await cb();
} finally {
await l.unlock();
}
}
async function buggy4<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
await l.lock();
try {
return await cb();
} finally {
// no waiting for unlock
l.unlock();
}
}
async function buggy5<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
// no waiting for either
l.lock();
try {
return await cb();
} finally {
l.unlock();
}
}
async function buggy6<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
// no waiting for anything
l.lock();
try {
return cb();
} finally {
l.unlock();
}
}
// wait for everything
async function okay1<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
await l.lock();
try {
return await cb();
} finally {
await l.unlock();
}
}
async function okay2<T>(l: Lock, cb: () => Promise<T>): Promise<T> {
await l.lock();
try {
// wait for everything, with an explicit promise
return Promise.resolve(await cb());
} finally {
await l.unlock();
}
}
// driver
(async () => {
for (const func of [buggy1, buggy2, buggy3, buggy4, buggy5, buggy6, okay1, okay2]) {
const name = func.name;
console.log(`${name}: .- startup`);
await func(new Lock(name), async () => {
await delay(20);
console.log(`${name}: | πŸ‘“ inside`)
});
console.log(`${name}: '- done`);
await delay(70);
}
})().then(() => console.log('app complete'));
function delay<T>(millis: number, value?: T): Promise<T> {
return new Promise((resolve) => setTimeout(() => resolve(value), millis))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment