Building Zitrone, part 2

Behind Zitrone app - reduced intermezzo

This part is co-authored by Jon Higger who helped me to point out the problem and explained the Promise logic behind. Last time I promised code and more practical value. I think that I should begin with my “async reducer trouble”, because the explanation kinda blows my mind. There is a good chance that you already run into similar issues and if you already write your reducers using `Promise.all` you can skip part 2 completely.

I wrote this code:

let events_attendance = list.keys.reduce(async function (
    previousValue: { event: { name: any; metadata: Object }; attendees: any }[],
    currentValue: { name: string; metadata: Object }
) {
    const attendees = await context.EVENTS_KV.get(
        `getattendance:${currentValue.name}`
    );
    console.log("current", await currentValue);
    console.log("prev", await previousValue);
    console.log(typeof previousValue);
    previousValue.push({
        event: currentValue,
        attendees: attendees?.split("'"),
    });
    return [...previousValue];
},
[]);

and it does not work. It does not work in a very interesting way, the result of console.log reveals that push to previousValue works for the first time (it pushes the value to the empty array) - value is displayed in console, but it immediately fails to push another value to accumulator.

Reducers. Reduces will to live.
Reducers. Reduces will to live.

I expected that the problem is somewhere in typescript (or better said, my limited experience) and I tried a lot of things - you don’t even see all the console logs I tried. Actually, one of the misguiding moments was trying typeof statement, because it always resolved to object. The problem is that it is an object only on the first run. In async reducer, current value becomes a promise and promise does not have a push method.

The universe is a dark and cold place
The universe is a dark and cold place

Awaiting inside of the reduce can solve this problem easily, but that is not the correct way to do that as we actually want to run the data operation asynchronously. The problem with the asynchronous reduce, is that you are awaiting each promise to return before asking for the next one. You can think of this as a series of promises for example if this is my array of promises:

const promises = [Promise1, Promise2, Promise3]

and we do an asynchronous reduce, then Promise1 will have to resolve, before the request for Promise2 is even sent.

In Contrast, we can request all of the Promises at effectively the same time using Promise.all

const resolved = await Promise.all([Promise1, Promise2, Promise3])

Now all of these promises ASK FOR DATA at the same time. But each Promise can resolve as it pleases. This allows us to remove the reducers, with the new and more elegant code being:

	interface Event {
		name: string;
		metadata: Object;
	}
	interface Events {
		events: Event[];
		attendees: string;
	}
	const getAttendance = (event: Event): Promise<Event> =>
		context.EVENTS_KV.get(`getattendance:${event.name}`)
			.then((res: string) => {
				return { ...event, attendees: res ? String(res) : "" };
			})
			.catch((err: any) => {
				console.log(err);
				return {};
			});

	const eventsList: Events[] = await Promise.all(
		list.keys.map((event: Event) => getAttendance(event))
	);

	return json(eventsList);

In the case of KV it does not yield anything else than compiler happiness, but it will help us to understand the original problem better. if you still struggle, the code above is actually similar to the MDN examples I’ve seen before and kind of forgot (thanks again for all the help Jon).

Thanks for reading the unusual Part 2, I hope that it was useful. Next time we will (really) get into KV and Cloudflare Pages and then I plan to return to Remix with an article about resource routes.

Subscribe to Adam Sobotka
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.