TypeScript generic async function wrapper function
September 12, 2021
0 comments JavaScript
I find this so fiddly! I love TypeScript and will continue to use it if there's a choice. But I just wanted to write a simple async function wrapper and I had to Google for it and nothing was quite right. Here's my simple solution, as an example:
function wrappedAsyncFunction<T>(
fn: (...args: any[]) => Promise<T>
): (...args: any[]) => Promise<T> {
return async function(...args: any[]) {
console.time("Took");
try {
return await fn(...args);
} catch(error) {
console.warn("FYI, an error happened:", error);
throw error;
} finally {
console.timeEnd("Took");
}
};
}
What I use it for is to wrap my Firebase Cloud Functions so that if any error happens, I can send that error to Rollbar. In particular, here's an example of it in use:
diff --git a/functions/src/cleanup-thumbnails.ts b/functions/src/cleanup-thumbnails.ts
index 46bdb34..a3e8d54 100644
--- a/functions/src/cleanup-thumbnails.ts
+++ b/functions/src/cleanup-thumbnails.ts
@@ -2,6 +2,8 @@ import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
import { logger } from "firebase-functions";
+import { wrappedLogError } from "./rollbar-logger";
+
const OLD_DAYS = 30 * 6; // 6 months
// const ADDITIONAL_DAYS_BACK = 5;
// const ADDITIONAL_DAYS_BACK = 15;
@@ -9,7 +11,7 @@ const PREFIX = "thumbnails";
export const scheduledCleanupThumbnails = functions.pubsub
.schedule("every 24 hours")
- .onRun(async () => {
+ .onRun(wrappedLogError(async () => {
logger.debug("Running scheduledCleanupThumbnails");
...
And my wrappedLogError
looks like this:
export function wrappedLogError<T>(
fn: (...args: any[]) => Promise<T>
): (...args: any[]) => Promise<T> {
return async function(...args: any[]) {
try {
return await fn(...args);
} catch (error) {
logError(error);
throw error;
}
};
}
I'm not sure it's the best or correct way to do it, but it seems to work. Perhaps there's a more correct solution but for now I'll ship this because it seems to work fine.
TypeScript function keyword arguments like Python
September 8, 2021
0 comments Python, JavaScript
To do this in Python:
def print_person(name="peter", dob=1979):
print(f"name={name}\tdob={dob}")
print_person()
# prints: name=peter dob=1979
print_person(name="Tucker")
# prints: name=Tucker dob=1979
print_person(dob=2013)
# prints: name=peter dob=2013
print_person(sex="boy")
# TypeError: print_person() got an unexpected keyword argument 'sex'
...in TypeScript:
function printPerson({
name = "peter",
dob = 1979
}: { name?: string; dob?: number } = {}) {
console.log(`name=${name}\tdob=${dob}`);
}
printPerson();
// prints: name=peter dob=1979
printPerson({});
// prints: name=peter dob=1979
printPerson({ name: "Tucker" });
// prints: name=Tucker dob=1979
printPerson({ dob: 2013 });
// prints: name=peter dob=2013
printPerson({ gender: "boy" })
// Error: Object literal may only specify known properties, and 'gender'
Here's a Playground copy of it.
It's not a perfect "transpose" across the two languages but it's sufficiently similar.
The trick is that last = {}
at the end of the function signature in TypeScript which makes it possible to omit keys in the passed-in object.
By the way, the pure JavaScript version of this is:
function printPerson({ name = "peter", dob = 1979 } = {}) {
console.log(`name=${name}\tdob=${dob}`);
}
But, unlike Python and TypeScript, you get no warnings or errors if you'd do printPerson({ gender: "boy" });
with the JavaScript version.
How I upload Firebase images optimized
September 2, 2021
0 comments JavaScript, Web development, Firebase
I have an app that allows you to upload images. The images are stored using Firebase Storage. Then, once uploaded I have a Firebase Cloud Function that can turn that into a thumbnail. The problem with this is that it takes a long time to wake up the cloud function, the first time, and generating that thumbnail. Not to mention the download of the thumbnail payload for the client. It's not unrealistic that the whole thumbnail generation plus download can take multiple (single digit) seconds. But you don't want to have the user sit and wait that long. My solution is to display the uploaded file in a <img>
tag using URL.createObjectURL()
.
The following code is most pseudo-code but should look familiar if you're used to how Firebase and React/Preact works. Here's the FileUpload
component:
interface Props {
onUploaded: ({ file, filePath }: { file: File; filePath: string }) => void;
onSaved?: () => void;
}
function FileUpload({
onSaved,
onUploaded,
}: Props) => {
const [file, setFile] = useState<File | null>(null);
// ...some other state stuff omitted for example.
useEffect(() => {
if (file) {
const metadata = {
contentType: file.type,
};
const filePath = getImageFullPath(prefix, item ? item.id : list.id, file);
const storageRef = storage.ref();
uploadTask = storageRef.child(filePath).put(file, metadata);
uploadTask.on(
"state_changed",
(snapshot) => {
// ...set progress percentage
},
(error) => {
setUploadError(error);
},
() => {
onUploaded({ file, filePath }); // THE IMPORTANT BIT!
db.collection("pictures")
.add({ filePath })
.then(() => { onSaved() })
}
}
}, [file])
return (
<input
type="file"
accept="image/jpeg, image/png"
onInput={(event) => {
if (event.target.files) {
const file = event.target.files[0];
validateFile(file);
setFile(file);
}
}}
/>
);
}
The important "trick" is that we call back after the storage is complete by sending the filePath
and the file
back to whatever component triggered this component. Now, you can know, in the parent component, that there's going to soon be an image reference with a file path (filePath
) that refers to that File
object.
Here's a rough version of how I use this <FileUpload>
component:
function Images() {
const [uploadedFiles, setUploadedFiles] = useState<Map<string, File>>(
new Map()
);
return (<div>
<FileUpload
onUploaded={({ file, filePath }: { file: File; filePath: string }) => {
const newMap: Map<string, File> = new Map(uploadedFiles);
newMap.set(filePath, file);
setUploadedFiles(newMap);
}}
/>
<ListUploadedPictures uploadedFiles={uploadedFiles}/>
</div>
);
}
function ListUploadedPictures({ uploadedFiles}: {uploadedFiles: Map<string, File>}) {
// Imagine some Firebase Firestore subscriber here
// that watches for uploaded pictures.
return <div>
{pictures.map(picture => (
<Picture picture={picture} uploadedFiles={uploadedFiles} />
))}
</div>
}
function Picture({
uploadedFiles,
picture,
}: {
uploadedFiles: Map<string, File>;
picture: {
filePath: string;
}
}) {
const thumbnailURL = getThumbnailURL(filePath, 500);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const preloadImg = new Image();
preloadImg.src = thumbnailURL;
const callback = () => {
if (mounted) {
setLoaded(true);
}
};
if (preloadImg.decode) {
preloadImg.decode().then(callback, callback);
} else {
preloadImg.onload = callback;
}
return () => {
mounted = false;
};
}, [thumbnailURL]);
return <img
style={{
width: 500,
height: 500,
"object-fit": "cover",
}}
src={
loaded
? thumbnailURL
: file
? URL.createObjectURL(file)
: PLACEHOLDER_IMAGE
}
/>
}
Phew! That was a lot of code. Sorry about that. But still, this is just a summary of the real application code.
The point is that; I send the File
object back to the parent component immediately after having uploaded it to Firebase Cloud Storage. Then, having access to that as a File
object, I can use that as the thumbnail while I wait for the real thumbnail to come in. Now, it doesn't matter that it takes 1-2 seconds to wake up the cloud function and 1-2 seconds to perform the thumbnail creation, and then 0.1-2 seconds to download the thumbnail. All the while this is happening you're looking at the File
object that was uploaded. Visually, the user doesn't even notice the difference. If you refresh the page, that temporary in-memory uploadedFiles
(Map
instance) is empty so you're now relying on the loading of the thumbnail which should hopefully, at this point, be stored in the browser's native HTTP cache.
The other important part of the trick is that we're using const preloadImg = new Image()
for loading the thumbnail. And by relying on preloadImage.decode ? preloadImage.decode().then(...) : preload.onload = ...
we can be informed only when the thumbnail has been successfully created and successfully downloaded to make the swap.
How to fadeIn and fadeOut like jQuery but with Cash
August 24, 2021
0 comments JavaScript
Remember jQuery? Yeah, it was great. But it was also horrible in its own ways but only when compared to the more powerful tools that we have now as of 2021. I still (almost) use it here on my site. Atually, I use "fork" of jQuery called Cash which calls itself: "An absurdly small jQuery alternative for modern browsers."
Cash is written in TypeScript, which gives me peace of mind, and as a JS bundle, it's only 19KB minified (5.3KB Brotli compressed) whereas jQuery is 87KB minified (27KB Brotli compressed).
But something that jQuery has, that Cash doesn't, is animations. E.g. $('myselector').fadeIn()
. If you need to do this with Cash you can use the following pure JavaScript solution:
// Example implementation
const msg = $('<div class="message">')
.text(`Random message: ${Math.random()}`)
.css("opacity", 0)
.css("transition", "opacity 600ms")
.prependTo($("#root"));
setTimeout(() => msg.css("opacity", 1), 0);
setTimeout(() => {
msg.css("transition", "opacity 1000ms").css("opacity", 0);
setTimeout(() => msg.remove(), 1000);
}, 3000);
What this application demonstrates is the creation of a <div>
that's immediately injected into the DOM but slowly fades into view. And 3 seconds later it fades out and is removed. Full demo/sample application here.
Sample application using cash
like jQuery's $.fadeIn()
.
The point of the demo is how you can cause the fade-in effect with just Cash but still relies on CSS for the actual animation.
The trick is to, ultimately, create it first like this:
<div class="message" style="opacity:0; transition: opacity 600ms">
Random message: 0.6517198324628395
</div>
and then, right after it's been added to the DOM, change the style=...
to:
-<div class="message" style="opacity:0; transition: opacity 600ms">
+<div class="message" style="opacity:1; transition: opacity 600ms">
What's neat about this is that you use the transition
shortcut so it's done entirely with CSS instead of a requestAnimationFrame
and/or while
-loop like jQuery's effects.js
does it.
Note! This is not a polyfill since jQuery's fadeIn()
(etc.) can do a lot more such as callbacks. The example might not be great but I hope this little solution becomes useful for someone else who needs this.
How to submit a form with Playwright
August 3, 2021
0 comments JavaScript
Because it was driving me insane, and because I don't want to ever forget...
Playwright is a wonderful alternative to jest-puppeteer
for doing automated headless browser end-to-end testing. But one I couldn't find in the documentation, Google search, or Stackoverflow was: How do you submit a form without clicking a button?. I.e. you have focus in an input
field and hit Enter. Here's how you do it:
await page.$eval('form[role="search"]', (form) => form.submit());
The first part is any CSS selector that gets you to the <form>
element. In this case, imagine it was:
<form action="/search" role="search">
<input type="search" name="q">
</form>
You, or my future self, might be laughing at me for missing something obvious but this one took me forever to solve so I thought I'd better blog about it in case someone else gets into the same jam.
UPDATE (Sep 2021)
I found a much easier way:
await page.keyboard.press("Enter");
This obviously only works when you've typed something into an input so the focus is on that <input>
element. E.g.:
await page.fill('input[aria-label="New shopping list item"]', "Carrots");
await page.keyboard.press("Enter");
What English stop words overlap with JavaScript reserved keywords?
May 7, 2021
2 comments JavaScript, MDN
The list of stop words in Elasticsearch is:
a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with
The list of JavaScript reserved keywords is:
abstract, arguments, await, boolean, break, byte, case, catch, char, class, const, continue, debugger, default, delete, do, double, else, enum, eval, export, extends, false, final, finally, float, for, function, goto, if, implements, import, in, instanceof, int, interface, let, long, native, new, null, package, private, protected, public, return, short, static, super, switch, synchronized, this, throw, throws, transient, true, try, typeof, var, void, volatile, while, with, yield
That means that the overlap is:
for, if, in, this, with
And the remainder of the English stop words is:
a, an, and, are, as, at, be, but, by, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, to, was, will
Why does this matter? It matters when you're writing a search engine on English text that is about JavaScript. Such as, MDN Web Docs. At the time of writing, you can search for this
because there's a special case explicitly for that word. But you can't search for for
which is unfortunate.
But there's more! I think we should consider certain prototype words to be considered "reserved" because they are important JavaScript words that should not be treated as stop words. For example...
of
- fromfor (const thing of things)
orArray.of(...)
String.prototype.at
Object.is()