Skip to content

Instantly share code, notes, and snippets.


Cameron Nokes ccnokes

View GitHub Profile
ccnokes / serial.ts
Last active Nov 17, 2022
Takes a list of async functions and executes them one at a time. You could just use a `for of` loop for this too 😅
View serial.ts
* takes a list of async functions and executes them one at a time
async function serial(
fnList: Array<() => Promise<void>>,
nextIndex?: number = 0,
) {
let current = fnList[nextIndex];
if (!current) return;
ccnokes / CacheStore.ts
Created Apr 12, 2021
A persistent, async store based on Cache API
View CacheStore.ts
* A persistent, async store based on Cache API
* NOTE this is experimental
type Options = {
name: string
version: number
userId: string
type: "json" | "text" | "blob"
View localeUtils.ts
function getMonths(locale: string, format: 'long' | 'short' = 'long') {
const monthNames = [];
const currentYear = new Date().getFullYear();
for (let i = 0; i < 12; i++) {
new Date(currentYear, i, 1).toLocaleDateString(locale, {
month: format
return monthNames;
ccnokes /
Last active Nov 21, 2022
Bash script that converts .HEIC to .jpg files
set -eu -o pipefail
count=$(find . -depth 1 -name "*.HEIC" | wc -l | sed 's/[[:space:]]*//')
echo "converting $count files .HEIC files to .jpg"
magick mogrify -monitor -format jpg *.HEIC
echo "Remove .HEIC files? [y/n]"
ccnokes / useCallIfMounted.tsx
Last active Apr 6, 2020
React hook that only runs a function if the component is still mounted. React says this is an anti-pattern, but vanilla Promises and async DOM APIs don't give you a way to cancel async actions 🤷‍♂️.
View useCallIfMounted.tsx
function useCallIfMounted() {
let isMounted = React.useRef(false);
React.useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, [isMounted]);
return React.useCallback((fn) => isMounted.current && fn(), [isMounted]);
ccnokes / memoize.ts
Last active Nov 1, 2020
A memoize function is only as good as its cache
View memoize.ts
// NOTE can't use lodash.memoize because we need 2 caches and v2 doesn't support replacing the cache implementation
// TS type note: the below type is inspired by Lodash v4 types
function memoize<T extends (...args: any) => any>(fn: T, resolver?: (...args: any[]) => any) {
let objectCache = new WeakMap<any, any>(); // WeakMap can only store objects as keys and allows the objects to get GC'ed out
let primitiveCache = new Map<any, any>(); // Map can store anything as a key but no auto GC
return function(...args: Parameters<T>): ReturnType<T> {
// just in case things got out of hand, dump the cache if too many entries
if (primitiveCache.size > 10000) {
ccnokes /
Last active Aug 15, 2021
Bash script that checks for apps that use Electron. Detailed a bit more here:
check() {
stat "$1/Contents/Frameworks/Electron Framework.framework" &> /dev/null
if [[ $? = 0 ]]; then
echo "$1 uses Electron"
ccnokes / ObservableMap.ts
Last active Nov 5, 2019
A Map class with built-in event emitter that emits a `update` event whenever something changes
View ObservableMap.ts
class ObservableMap<K, V> extends Map<K, V> {
readonly events = new EventEmitter();
set(key: K, value: V) {
let previousValue = this.get(key);
super.set(key, value);"update", {
action: "set",
currentValue: this.get(key)
View event-emitter.js
class EventEmitter {
constructor() { = new EventTarget();
on(eventName, listener) {
return, listener);
once(eventName, listener) {
return, listener, { once: true });
ccnokes / event-emitter.js
Created Oct 30, 2019
Event emitter using the native DOM APIs: EventTarget and CustomEvent
View event-emitter.js
// Who needs eventemitter3, mitt, or some other library when you can use native DOM APIs? 😁
let eventEmitter = new EventTarget();
eventEmitter.addEventListener('test', console.log); // CustomEvent { type: 'test', detail: 123, ... }
eventEmitter.dispatchEvent(new CustomEvent('test', { detail: 123 }));