在 express res.send() 中合并各种后端请求

Merging various backend requests in the express res.send()

我正在尝试进行多个异步后端调用以在我的 express API 中生成 JSON 响应。由于 API 的性质,我提出了 3 个在某种程度上相互依赖的请求。

Request 1: Returns an Array of values that are used to make request 2. Each value will be used as a mapping for the remaining requests. That is to say, it will be a unique identifier used to map the response from the requests in Request 3.

Request 2 (Parallel Batch): A request is made using each value from the Array returned in request 1. Each of these returns a value to be used in each of the Request 3s. That is to say, it's a 1-to-1

Request 3 (Parallel Batch): This request takes the response from Request 2, and makes a 1-to-1 follow up request to get more data on that specific mapping (the id from request 1)

我希望发送给消费者的最终数据如下所示:

{
  id1: details1,
  id2: details2,
  id3: details3,
  ...
}

这是我目前的代码...

app.get("/artists/:artist/albums", (req, res) => {
  console.log("#############")
  const artistName = req.params.artist
  let response = {};
  let s3Promise = s3.listAlbums(artistName)
  let albumDetailsPromises = []

  s3Promise
    .then((data) => {
      data.map((album) => {
        // Each album name here will actually be used as the unique identifier for 
        // the final response
        
        // Build an Array of promises that will first fetch the albumId, then use
        // that album id to fetch the details on the album
        albumDetailsPromises.push(
          discogs.getAlbumId(artistName, album).then(  // Returns a promise
            ({ data }) => {
              let masterId = data.results[0].id
              let recordName = data.results[0].title

              // Storing the album name to carry as a unique id alongside the promise
              return [album, discogs.getAlbumDetails(masterId) // Returns a promise ]
            }
          )
        )
      })
    })
    .then(() => {
      // When all the albumIds have been fetched, there will still exist a promise in the 
      // second index of each element in the albumDetailsPromises array
      Promise.all(albumDetailsPromises)
        .then((namedPromises) => {
          namedPromises.map(
            (album) => {
              let albumName = album[0]  // Unique Id
              let albumDetailPromise = album[1] 

              // Resolving the albumDetailsPromise here, and storing the value on
              // a response object that we intend to send as the express response
              albumDetailPromise
                .then(
                  ({ data }) => {
                    response[albumName] = data
                  })
                .catch(err => response[albumName] = err)
            })
        })
    })
    .catch((err) => console.log(err))
})

到目前为止,一切似乎都在按预期进行,我似乎无法弄清楚如何“等待”在所有这些 Promise 结束时更新的响应对象。我从这个例子中省略了 res.send(response),因为它不起作用,但这当然是我想要的结果。

如有任何建议,我们将不胜感激!新 javascript...

我建议使用 async/await 重写它,因为它有助于减少嵌套。您还可以将获取专辑详细信息的逻辑提取到一个单独的函数中,因为这也增加了代码的可读性。像这样的东西(这仍然需要错误处理,但它应该给你一个开始):

app.get("/artists/:artist/albums", async (req, res) => {
    const artistName = req.params.artist;
    const albumNames = await s3.listAlbums(artistName);
    const result = {};
    
    const albumDetailPromises = albumNames.map(albumName => requestAlbumDetails(discogs, artistName, albumName));
    const resolvedAlbumDetails = await Promise.all(albumDetailPromises);
    
    // map desired response structure
    for(const albumDetail of resolvedAlbumDetails) {
      result[albumDetail.albumName] = albumDetail.albumDetails;
    }
    
    res.json(result);
});

async function requestAlbumDetails(service, artistName, albumName) {
    const albumInfo = await service.getAlbumId(artistName, albumName);
    const masterId = albumInfo.results[0].id;
    const albumDetails = await service.getAlbumDetails(masterId);
    return { albumName, albumDetails };
}

要回答您的问题,您可以如何使用您的代码来做到这一点: 您需要等待使用另一个 Promise.all 调用完成所有详细信息,然后 send then-处理程序中的响应:

Promise.all(albumDetailsPromises)
  .then((namedPromises) => {
    const detailsPromises = namedPromises.map(
      (album) => {
        let albumName = album[0];
        let albumDetailPromise = album[1];

        return albumDetailPromise
          .then(({ data }) => {
            response[albumName] = data;
          })
          .catch(err => response[albumName] = err);
      });
    return Promise.all(detailsPromises)
      .then(() => res.json(response));
  })

重构使用async/await...

app.get("/artists/:artist/albums", async (req, res) => {
  const artistName = req.params.artist
  let response = {};

  let albums = await s3.listAlbums(artistName)
  const promises = albums.map(async (album) => {
    let result = await discogs.getAlbumId(artistName, album)
    try {
      let masterId = result.data.results[0].id
      let tempRes = await discogs.getAlbumDetails(masterId)
      return [album, tempRes.data]
    } catch (error) {
      return [album, { "msg": error.message }]
    }
  })

  responses = await Promise.all(promises)
  responses.map(data => { response[data[0]] = data[1] })

  res.send(response)
})