oscklm
oscklm•12mo ago

Issue with filtering out nulls from a promise.all mapping over some document ids

export const getById = zQuery({
args: { id: zid('videoCollection') },
handler: async (ctx, { id }) => {
// Get the collection
const collection = await ctx.db.get(id)
if (!collection) return null

// Get the videos from the collection videos
const videos = await Promise.all(collection.videos.map((videoId) => ctx.db.get(videoId))).then(
(results) => results.filter((r) => r !== null),
) // <- ISSUE: this currently returns null, even with the filter, because ctx.db.get() can return null

// Enrich the videos
const enrichedVideos = await Promise.all(videos.map(async (video) => enrichVideo(ctx, video)))

// Return the collection including the videos
return { title: collection.title, videos }
},
})
export const getById = zQuery({
args: { id: zid('videoCollection') },
handler: async (ctx, { id }) => {
// Get the collection
const collection = await ctx.db.get(id)
if (!collection) return null

// Get the videos from the collection videos
const videos = await Promise.all(collection.videos.map((videoId) => ctx.db.get(videoId))).then(
(results) => results.filter((r) => r !== null),
) // <- ISSUE: this currently returns null, even with the filter, because ctx.db.get() can return null

// Enrich the videos
const enrichedVideos = await Promise.all(videos.map(async (video) => enrichVideo(ctx, video)))

// Return the collection including the videos
return { title: collection.title, videos }
},
})
Tried quite a few different methods of filtering out the nulls from this promise.all. Unsure if this approach is just generally not the way to go about it? Am i on the wrong track here
6 Replies
oscklm
oscklmOP•12mo ago
Hmm, this type guard seems to do the trick, still open for other potentially better solutions if anyone else has played around with this themselves:
// Get the videos from the collection videos
const videos = (
await Promise.all(collection.videos.map((videoId) => ctx.db.get(videoId)))
).filter((video): video is NonNullable<Doc<'video'>> => video !== null)
// Get the videos from the collection videos
const videos = (
await Promise.all(collection.videos.map((videoId) => ctx.db.get(videoId)))
).filter((video): video is NonNullable<Doc<'video'>> => video !== null)
erquhart
erquhart•12mo ago
Just filter the whole array after mapping. You can also enrich in the initial mapping to pair things down:
export const getById = zQuery({
args: { id: zid('videoCollection') },
handler: async (ctx, { id }) => {
export const getById = zQuery({
args: { id: zid('videoCollection') },
handler: async (ctx, { id }) => {
const collection = await ctx.db.get(id)
if (!collection) return null

const videos = (await Promise.all(
collection.videos.map(async (videoId) => {
const video = await ctx.db.get(videoId)
if (video) {
return enrichVideo(ctx, video)
}
}),
)).filter(Boolean)

return { title: collection.title, videos }
},
})

},
})
export const getById = zQuery({
args: { id: zid('videoCollection') },
handler: async (ctx, { id }) => {
export const getById = zQuery({
args: { id: zid('videoCollection') },
handler: async (ctx, { id }) => {
const collection = await ctx.db.get(id)
if (!collection) return null

const videos = (await Promise.all(
collection.videos.map(async (videoId) => {
const video = await ctx.db.get(videoId)
if (video) {
return enrichVideo(ctx, video)
}
}),
)).filter(Boolean)

return { title: collection.title, videos }
},
})

},
})
Wrapping the awaited Promise.all in parens allows us to filter directly on the resulting array. Boolean as a filter just drops everyting that isn't truthy.
oscklm
oscklmOP•12mo ago
Actually i had tried the .filter(Boolean) at first, but you've just made me realise what was missing. The freaking async and await inside of the map I had tried that before, storing it in a const and then checking if video, but my lack of making it async, still caused the null, so i moved onto trying a different solution 😆 It all makes sense now - thanks mate Ended up adding a type predicate, so only valid videos are getting returned:
function isValidVideo(video: any): video is VideoEnriched {
return video !== undefined && video !== null
}
function isValidVideo(video: any): video is VideoEnriched {
return video !== undefined && video !== null
}
erquhart
erquhart•12mo ago
Were you still getting nulls or was it an issue with the enrich function
oscklm
oscklmOP•12mo ago
Getting undefined was the issue. From what i could tell, the map just returned undefined if video was false And i wanted to make sure i got back neither undefined videos or null. But only valid videos Even though the type guard works perfectly fine. Cant help but to think whether I’m just completely misunderstanding something here.
erquhart
erquhart•12mo ago
Yeah the code I shared can only produce a videos array of truthy values, so null and undefined both would be filtered out. Did the undefined results start after a change? The type guard you’re using is functionally just a filter to specifically filter out null and undefined, which is a subset of what a Boolean filter will do.

Did you find this page helpful?