diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js --- a/lib/selectors/chat-selectors.js +++ b/lib/selectors/chat-selectors.js @@ -23,7 +23,7 @@ import { getSidebarItems, getAllInitialSidebarItems, - getAllFinalSidebarItems, + getCandidateSidebarItemsForThreadList, type SidebarItem, } from '../shared/sidebar-item-utils.js'; import { threadInChatList, threadIsPending } from '../shared/thread-utils.js'; @@ -111,12 +111,14 @@ const getFinalChatThreadItem = async () => { const lastUpdatedTimePromise = lastUpdatedTime(); + const candidateSidebarItemsPromise = + getCandidateSidebarItemsForThreadList(sidebars); + const lastUpdatedTimeIncludingSidebarsPromise = (async () => { - const lastUpdatedTimePromises = [ - lastUpdatedTimePromise, - ...sidebars.map(sidebar => sidebar.lastUpdatedTime()), - ]; - const lastUpdatedTimes = await Promise.all(lastUpdatedTimePromises); + const candidateSidebarItems = await candidateSidebarItemsPromise; + const lastUpdatedTimes = candidateSidebarItems.map( + sidebarItem => sidebarItem.lastUpdatedTime, + ); const max = lastUpdatedTimes.reduce((a, b) => Math.max(a, b), -1); return max; })(); @@ -124,16 +126,16 @@ const [ lastUpdatedTimeResult, lastUpdatedTimeIncludingSidebars, - allSidebarItems, + candidateSidebarItems, ] = await Promise.all([ lastUpdatedTimePromise, lastUpdatedTimeIncludingSidebarsPromise, - getAllFinalSidebarItems(sidebars), + candidateSidebarItemsPromise, ]); return { ...chatThreadItemBase, - sidebars: getSidebarItems(allSidebarItems), + sidebars: getSidebarItems(candidateSidebarItems), lastUpdatedTime: lastUpdatedTimeResult, lastUpdatedTimeIncludingSidebars: lastUpdatedTimeIncludingSidebars, }; diff --git a/lib/shared/sidebar-item-utils.js b/lib/shared/sidebar-item-utils.js --- a/lib/shared/sidebar-item-utils.js +++ b/lib/shared/sidebar-item-utils.js @@ -8,6 +8,7 @@ maxUnreadSidebars, type SidebarInfo, } from '../types/thread-types.js'; +import ChatThreadItemLoaderCache from '../utils/chat-thread-item-loader-cache.js'; import { threeDays } from '../utils/date-utils.js'; export type SidebarThreadItem = { @@ -113,4 +114,82 @@ return await Promise.all(allSidebarItemPromises); } -export { getSidebarItems, getAllInitialSidebarItems, getAllFinalSidebarItems }; +type SidebarItemForCache = { + +threadInfo: ThreadInfo, + +mostRecentNonLocalMessage: ?string, + +lastUpdatedTimeIncludingSidebars: number, +}; + +// This async function returns a set of candidates that can be passed to +// getSidebarItems. It avoids passing all of the sidebarInfos so that we don't +// need to `await lastUpdatedTime()` for all of them, which we've found can be +// expensive on Hermes. Instead, we use ChatThreadItemLoaderCache to only +// consider the top N candidates, such that passing the results to +// getSidebarItems would yield the same set as if we had processed every single +// sidebar. +async function getCandidateSidebarItemsForThreadList( + sidebarInfos: $ReadOnlyArray, +): Promise<$ReadOnlyArray> { + const loaders = sidebarInfos.map(sidebar => ({ + threadInfo: sidebar.threadInfo, + lastUpdatedAtLeastTimeIncludingSidebars: sidebar.lastUpdatedAtLeastTime, + lastUpdatedAtMostTimeIncludingSidebars: sidebar.lastUpdatedAtMostTime, + initialChatThreadItem: { + threadInfo: sidebar.threadInfo, + mostRecentNonLocalMessage: sidebar.mostRecentNonLocalMessage, + lastUpdatedTimeIncludingSidebars: sidebar.lastUpdatedAtLeastTime, + }, + getFinalChatThreadItem: async () => { + const lastUpdatedTime = await sidebar.lastUpdatedTime(); + return { + threadInfo: sidebar.threadInfo, + mostRecentNonLocalMessage: sidebar.mostRecentNonLocalMessage, + lastUpdatedTimeIncludingSidebars: lastUpdatedTime, + }; + }, + })); + + // We want the top maxReadSidebars threads (either read or unread), + // and the top maxUnreadSidebars unread threads + const generalCache = new ChatThreadItemLoaderCache( + loaders, + ); + const unreadCache = new ChatThreadItemLoaderCache( + loaders.filter(loader => loader.threadInfo.currentUser.unread), + ); + + const topGeneralPromise = + generalCache.loadMostRecentChatThreadItems(maxReadSidebars); + const topUnreadPromise = + unreadCache.loadMostRecentChatThreadItems(maxUnreadSidebars); + const [topGeneralResults, topUnreadResults] = await Promise.all([ + topGeneralPromise, + topUnreadPromise, + ]); + + const topResults = [ + ...topGeneralResults.slice(0, maxReadSidebars), + ...topUnreadResults.slice(0, maxUnreadSidebars), + ]; + + return topResults.map(result => { + const { + threadInfo, + mostRecentNonLocalMessage, + lastUpdatedTimeIncludingSidebars, + } = result; + return { + type: 'sidebar', + threadInfo, + lastUpdatedTime: lastUpdatedTimeIncludingSidebars, + mostRecentNonLocalMessage, + }; + }); +} + +export { + getSidebarItems, + getAllInitialSidebarItems, + getAllFinalSidebarItems, + getCandidateSidebarItemsForThreadList, +};