refactor: replace Promise.all with Promise.allSettled to handle external API failures more gracefully

This commit is contained in:
Pierre
2025-03-16 13:18:20 +01:00
committed by HiItsStolas
parent a7e34de2dd
commit 0341c705ef
5 changed files with 181 additions and 107 deletions

View File

@@ -83,8 +83,7 @@ artistRoutes.get('/:id', async (req, res, next) => {
const mbIds = releaseGroupsToProcess.map((rg) => rg.mbid);
const [artistWikipedia, artistImages, relatedMedia, albumMetadata] =
await Promise.all([
const responses = await Promise.allSettled([
musicbrainz
.getArtistWikipediaExtract({
artistMbid: req.params.id,
@@ -101,6 +100,15 @@ artistRoutes.get('/:id', async (req, res, next) => {
}),
]);
const artistWikipedia =
responses[0].status === 'fulfilled' ? responses[0].value : null;
const artistImages =
responses[1].status === 'fulfilled' ? responses[1].value : null;
const relatedMedia =
responses[2].status === 'fulfilled' ? responses[2].value : [];
const albumMetadata =
responses[3].status === 'fulfilled' ? responses[3].value : [];
const metadataMap = new Map(
albumMetadata.map((metadata) => [metadata.mbAlbumId, metadata])
);

View File

@@ -1294,17 +1294,18 @@ discoverRoutes.get('/music/artists', async (req, res, next) => {
};
}
const [artistImageResults] = await Promise.all([
const responses = await Promise.allSettled([
artistsNeedingImages.length > 0
? (theAudioDb.batchGetArtistImages(
artistsNeedingImages
) as Promise<ArtistImageResults>)
: ({} as ArtistImageResults),
? theAudioDb.batchGetArtistImages(artistsNeedingImages)
: Promise.resolve({} as ArtistImageResults),
artistsForPersonMapping.length > 0
? personMapper.batchGetMappings(artistsForPersonMapping)
: {},
: Promise.resolve({}),
]);
const artistImageResults =
responses[0].status === 'fulfilled' ? responses[0].value : {};
let updatedArtistMetadata = artistMetadata;
if (artistsForPersonMapping.length > 0 || artistsNeedingImages.length > 0) {
updatedArtistMetadata = await getRepository(MetadataArtist).find({

View File

@@ -59,7 +59,7 @@ musicRoutes.get('/:id', async (req, res, next) => {
metadataArtist,
trackArtistMetadata,
artistWikipedia,
] = await Promise.all([
] = await Promise.allSettled([
getRepository(MetadataAlbum).findOne({
where: { mbAlbumId: req.params.id },
}),
@@ -81,6 +81,17 @@ musicRoutes.get('/:id', async (req, res, next) => {
: Promise.resolve(null),
]);
const resolvedMetadataAlbum =
metadataAlbum.status === 'fulfilled' ? metadataAlbum.value : null;
const resolvedMetadataArtist =
metadataArtist.status === 'fulfilled' ? metadataArtist.value : undefined;
const resolvedTrackArtistMetadata =
trackArtistMetadata.status === 'fulfilled'
? trackArtistMetadata.value
: [];
const resolvedArtistWikipedia =
artistWikipedia.status === 'fulfilled' ? artistWikipedia.value : null;
const trackArtistsToMap = albumDetails.mediums
.flatMap((medium) => medium.tracks)
.flatMap((track) =>
@@ -88,7 +99,7 @@ musicRoutes.get('/:id', async (req, res, next) => {
.filter((artist) => artist.artist_mbid)
.filter(
(artist) =>
!trackArtistMetadata.some(
!resolvedTrackArtistMetadata.some(
(m) => m.mbArtistId === artist.artist_mbid && m.tmdbPersonId
)
)
@@ -98,12 +109,13 @@ musicRoutes.get('/:id', async (req, res, next) => {
}))
);
const [artistImages, personMappingResult, updatedArtistMetadata] =
await Promise.all([
artistId && !metadataArtist?.tadbThumb && !metadataArtist?.tadbCover
const responses = await Promise.allSettled([
artistId &&
!resolvedMetadataArtist?.tadbThumb &&
!resolvedMetadataArtist?.tadbCover
? theAudioDb.getArtistImages(artistId)
: Promise.resolve(null),
artistId && isPerson && !metadataArtist?.tmdbPersonId
artistId && isPerson && !resolvedMetadataArtist?.tmdbPersonId
? personMapper
.getMapping(
artistId,
@@ -117,25 +129,34 @@ musicRoutes.get('/:id', async (req, res, next) => {
where: { mbArtistId: In(trackArtistIds) },
})
)
: Promise.resolve(trackArtistMetadata),
: Promise.resolve(resolvedTrackArtistMetadata),
]);
const artistImages =
responses[0].status === 'fulfilled' ? responses[0].value : null;
const personMappingResult =
responses[1].status === 'fulfilled' ? responses[1].value : null;
const updatedArtistMetadata =
responses[2].status === 'fulfilled'
? responses[2].value
: resolvedTrackArtistMetadata;
const updatedMetadataArtist =
personMappingResult && artistId
? await getRepository(MetadataArtist).findOne({
where: { mbArtistId: artistId },
})
: metadataArtist;
: resolvedMetadataArtist;
const mappedDetails = mapMusicDetails(albumDetails, media, onUserWatchlist);
const finalTrackArtistMetadata =
updatedArtistMetadata || trackArtistMetadata;
updatedArtistMetadata || resolvedTrackArtistMetadata;
return res.status(200).json({
...mappedDetails,
posterPath: metadataAlbum?.caaUrl ?? null,
needsCoverArt: !metadataAlbum?.caaUrl,
artistWikipedia,
posterPath: resolvedMetadataAlbum?.caaUrl ?? null,
needsCoverArt: !resolvedMetadataAlbum?.caaUrl,
artistWikipedia: resolvedArtistWikipedia,
artistThumb:
updatedMetadataArtist?.tmdbThumb ??
updatedMetadataArtist?.tadbThumb ??
@@ -202,15 +223,20 @@ musicRoutes.get('/:id/artist', async (req, res, next) => {
});
}
const [artistDetails, cachedTheAudioDb, metadataArtist] = await Promise.all(
[
const responses = await Promise.allSettled([
listenbrainzApi.getArtist(artistData.artist_mbid),
theAudioDb.getArtistImagesFromCache(artistData.artist_mbid),
metadataArtistRepository.findOne({
where: { mbArtistId: artistData.artist_mbid },
}),
]
);
]);
const artistDetails =
responses[0].status === 'fulfilled' ? responses[0].value : null;
const cachedTheAudioDb =
responses[1].status === 'fulfilled' ? responses[1].value : null;
const metadataArtist =
responses[2].status === 'fulfilled' ? responses[2].value : null;
if (!artistDetails) {
return res.status(404).json({ status: 404, message: 'Artist not found' });
@@ -229,8 +255,7 @@ musicRoutes.get('/:id/artist', async (req, res, next) => {
const similarArtistIds =
artistDetails.similarArtists?.artists?.map((a) => a.artist_mbid) ?? [];
const [relatedMedia, albumMetadata, similarArtistMetadata] =
await Promise.all([
const mediaResponses = await Promise.allSettled([
Media.getRelatedMedia(req.user, releaseGroupIds),
metadataAlbumRepository.find({
where: { mbAlbumId: In(releaseGroupIds) },
@@ -242,6 +267,13 @@ musicRoutes.get('/:id/artist', async (req, res, next) => {
: Promise.resolve([]),
]);
const relatedMedia =
mediaResponses[0].status === 'fulfilled' ? mediaResponses[0].value : [];
const albumMetadata =
mediaResponses[1].status === 'fulfilled' ? mediaResponses[1].value : [];
const similarArtistMetadata =
mediaResponses[2].status === 'fulfilled' ? mediaResponses[2].value : [];
const albumMetadataMap = new Map(
albumMetadata.map((metadata) => [metadata.mbAlbumId, metadata])
);
@@ -272,8 +304,7 @@ musicRoutes.get('/:id/artist', async (req, res, next) => {
{ artistThumb: string | null; artistBackground: string | null }
>;
const [artistImageResults, updatedArtistMetadata, artistImagesResult] =
await Promise.all([
const artistResponses = await Promise.allSettled([
artistsNeedingImages.length > 0
? theAudioDb.batchGetArtistImages(artistsNeedingImages)
: ({} as ArtistImageResults),
@@ -291,6 +322,17 @@ musicRoutes.get('/:id/artist', async (req, res, next) => {
: Promise.resolve(null),
]);
const artistImageResults =
artistResponses[0].status === 'fulfilled' ? artistResponses[0].value : {};
const updatedArtistMetadata =
artistResponses[1].status === 'fulfilled'
? artistResponses[1].value
: similarArtistMetadata;
const artistImagesResult =
artistResponses[2].status === 'fulfilled'
? artistResponses[2].value
: null;
const relatedMediaMap = new Map(
relatedMedia.map((media) => [media.mbId, media])
);

View File

@@ -96,8 +96,7 @@ personRoutes.get('/:id', async (req, res, next) => {
const allReleaseGroupIds = releaseGroupsToProcess.map((rg) => rg.mbid);
const [artistImagesPromise, relatedMedia, albumMetadata] =
await Promise.all([
const responses = await Promise.allSettled([
!existingMetadata.tadbThumb && !existingMetadata.tadbCover
? theAudioDb.getArtistImages(existingMetadata.mbArtistId)
: Promise.resolve(null),
@@ -109,6 +108,13 @@ personRoutes.get('/:id', async (req, res, next) => {
}),
]);
const artistImagesPromise =
responses[0].status === 'fulfilled' ? responses[0].value : null;
const relatedMedia =
responses[1].status === 'fulfilled' ? responses[1].value : [];
const albumMetadata =
responses[2].status === 'fulfilled' ? responses[2].value : [];
if (artistImagesPromise) {
existingMetadata.tadbThumb = artistImagesPromise.artistThumb;
existingMetadata.tadbCover = artistImagesPromise.artistBackground;

View File

@@ -17,7 +17,6 @@ import { In } from 'typeorm';
const searchRoutes = Router();
const ITEMS_PER_PAGE = 20;
searchRoutes.get('/', async (req, res, next) => {
const queryString = req.query.query as string;
const page = Number(req.query.page) || 1;
@@ -42,7 +41,7 @@ searchRoutes.get('/', async (req, res, next) => {
const theAudioDb = TheAudioDb.getInstance();
const personMapper = TmdbPersonMapper.getInstance();
const [tmdbResults, albumResults, artistResults] = await Promise.all([
const responses = await Promise.allSettled([
tmdb.searchMulti({
query: queryString,
page,
@@ -50,14 +49,23 @@ searchRoutes.get('/', async (req, res, next) => {
}),
musicbrainz.searchAlbum({
query: queryString,
limit: ITEMS_PER_PAGE,
limit: 20,
}),
musicbrainz.searchArtist({
query: queryString,
limit: ITEMS_PER_PAGE,
limit: 20,
}),
]);
const tmdbResults =
responses[0].status === 'fulfilled'
? responses[0].value
: { page: 1, results: [], total_pages: 1, total_results: 0 };
const albumResults =
responses[1].status === 'fulfilled' ? responses[1].value : [];
const artistResults =
responses[2].status === 'fulfilled' ? responses[2].value : [];
const personIds = tmdbResults.results
.filter(
(result) => result.media_type === 'person' && !result.profile_path
@@ -162,7 +170,7 @@ searchRoutes.get('/', async (req, res, next) => {
{ artistThumb: string | null; artistBackground: string | null }
>;
const [personMappingResults, artistImageResults] = await Promise.all([
const externalApiResponses = await Promise.allSettled([
artistsNeedingMapping.length > 0
? personMapper.batchGetMappings(artistsNeedingMapping)
: ({} as PersonMappingResult),
@@ -171,6 +179,15 @@ searchRoutes.get('/', async (req, res, next) => {
: ({} as ArtistImageResult),
]);
const personMappingResults =
externalApiResponses[0].status === 'fulfilled'
? externalApiResponses[0].value
: ({} as PersonMappingResult);
const artistImageResults =
externalApiResponses[1].status === 'fulfilled'
? externalApiResponses[1].value
: ({} as ArtistImageResult);
let updatedArtistsMetadataMap = artistsMetadataMap;
if (
(artistsNeedingMapping.length > 0 || artistsNeedingImages.length > 0) &&
@@ -244,7 +261,7 @@ searchRoutes.get('/', async (req, res, next) => {
const totalItems = tmdbResults.total_results + musicResults.length;
const totalPages = Math.max(
tmdbResults.total_pages,
Math.ceil(totalItems / ITEMS_PER_PAGE)
Math.ceil(totalItems / 20)
);
const combinedResults =