1

我对使用 NodeJS 比较陌生,我正在做一个练习项目,使用 Youtube API 来获取用户视频的一些数据。Youtube API 返回带有页面令牌的视频列表,要成功收集用户的所有视频,您必须发出多个 API 请求,每个请求都有不同的页面令牌。当您到达这些请求的末尾时,响应中将不存在新的页面令牌,因此您可以继续。在 for 或 while 循环中执行此操作似乎是处理此问题的方法,但这些是同步操作,似乎无法在 Promise 中工作,因此我不得不寻找替代方法

我查看了一些以前对类似问题的答案,包括这里这里的答案. 我对答案中的代码有了大致的了解,但我无法完全弄清楚如何让它自己完全工作。我正在发出的请求已经链接在先前 API 调用的 .then() 中 - 我想使用新的页面令牌完成递归提取调用,然后转到另一个 .then()。现在,当我运行我的代码时,它会移动到下一个 .then() ,而没有完成使用令牌的请求。有没有办法阻止这种情况发生?我知道 async/await 可能是一个解决方案,但我决定在这里发布只是为了看看是否有任何可能的解决方案而不必走那条路,希望我能学到一些关于 fetch/promises 的知识。任何其他关于代码结构方式的建议/建议也很受欢迎,因为我

代码 :

let body = req.body
let resData = {}
let channelId = body.channelId
let videoData = []
let pageToken = ''

const fetchWithToken = (nextPageToken) => { 
            
    let uploadedVideosUrlWithToken = `https://youtube.googleapis.com/youtube/v3/playlistItems?part=ContentDetails&playlistId=${uploadedVideosPlaylistId}&pageToken=${nextPageToken}&maxResults=50&key=${apiKey}`

    fetch(uploadedVideosUrlWithToken)
    .then(res => res.json())
    .then(uploadedVideosTokenPart => {
        let {items} = uploadedVideosTokenPart
        videoData.push(...items.map(v => v.contentDetails.videoId))
        pageToken = (uploadedVideosTokenPart.nextPageToken) ? uploadedVideosTokenPart.nextPageToken : ''
            if (pageToken) {
                fetchWithToken(pageToken)
            } else {
                // tried to return a promise so I can chain .then() to it?
                // return new Promise((resolve) => {
                //     return(resolve(true))
                // })
            }
    })
}
const channelDataUrl = `https://youtube.googleapis.com/youtube/v3/channels?part=snippet%2CcontentDetails%2Cstatistics&id=${channelId}&key=${apiKey}`

// promise for channel data
// get channel data then store it in variable (resData) that will eventually be sent as a response, 
// contentDetails.relatedPlaylists.uploads is the playlist ID which will be used to get individual video data.

fetch(channelDataUrl)
    .then(res => res.json())
    .then(channelData => {
        let {snippet, contentDetails, statistics } = channelData.items[0]
        resData.snippet = snippet
        resData.statistics = statistics
        resData.uploadedVideos = contentDetails.relatedPlaylists.uploads
        return resData.uploadedVideos
    })
    .then(uploadedVideosPlaylistId => {

        // initial call to get first set of videos + first page token

        let uploadedVideosUrl = `https://youtube.googleapis.com/youtube/v3/playlistItems?part=ContentDetails&playlistId=${uploadedVideosPlaylistId}&maxResults=50&key=${apiKey}`

        fetch(uploadedVideosUrl)
            .then(res => res.json())
            .then(uploadedVideosPart => {
                let {nextPageToken, items} = uploadedVideosPart
                videoData.push(...items.map(v => v.contentDetails.videoId))
                // idea is to do api calls until pageToken is non existent, and add the video id's to the existing array.
                fetchWithToken(nextPageToken)

            })

        })
        .then(() => {

            // can't seem to get here synchronously - code in this block will happen before all the fetchWithToken's are complete - need to figure this out

        })

感谢任何抽出时间阅读本文的人。

编辑:

经过一些试验和错误,这似乎奏效了——这完全是一团糟。我理解它的方式是,这个函数现在递归地创建承诺,只有当 api 响应中没有页面标记时才会解析为 true,从而允许我从 .then() 返回这个函数并继续到新的 .then()同步。我仍然对更好的解决方案感兴趣,或者只是建议使这段代码更具可读性,因为我认为它根本不是很好。

    const fetchWithToken = (playlistId, nextPageToken) => { 
            
    let uploadedVideosUrlWithToken = `https://youtube.googleapis.com/youtube/v3/playlistItems?part=ContentDetails&playlistId=${playlistId}&pageToken=${nextPageToken}&maxResults=50&key=${apiKey}`


    return new Promise((resolve) => {
        resolve( new Promise((res) => { 
            fetch(uploadedVideosUrlWithToken)
                .then(res => res.json())
                .then(uploadedVideosTokenPart => {
                    let {items} = uploadedVideosTokenPart
                    videoData.push(...items.map(v => v.contentDetails.videoId))
                    pageToken = (uploadedVideosTokenPart.nextPageToken) ? uploadedVideosTokenPart.nextPageToken : ''
                        // tried to return a promise so I can chain .then() to it?
                        if (pageToken) {
                            res(fetchWithToken(playlistId, pageToken))
                        } else {
                            res(new Promise(r => r(true)))
                        }
                    })
                }))
            })
}
4

1 回答 1

0

使用 async/await 会好得多,它们基本上是 promise 的包装器。Promise 链接,这就是你对嵌套 then 所做的事情,可能会变得混乱和混乱......

我将您的代码转换为使用 async/await,因此希望这将帮助您了解如何解决您的问题。祝你好运!

您的初始代码:

let { body } = req
let resData = {}
let { channelId } = body
let videoData = []
let pageToken = ''

const fetchWithToken = async (nextPageToken) => {
  const someData = (
    await fetch(
      `https://youtube.googleapis.com/youtube/v3/playlistItems?part=ContentDetails&playlistId=${uploadedVideosPlaylistId}&pageToken=${nextPageToken}&maxResults=50&key=${apiKey}`,
    )
  ).json()

  let { items } = someData
  videoData.push(...items.map((v) => v.contentDetails.videoId))
  pageToken = someData.nextPageToken ? someData.nextPageToken : ''
  if (pageToken) {
    await fetchWithToken(pageToken)
  } else {
        // You would need to work out
  }
}

const MainMethod = async () => {
  const channelData = (
    await fetch(
      `https://youtube.googleapis.com/youtube/v3/channels?part=snippet%2CcontentDetails%2Cstatistics&id=${channelId}&key=${apiKey}`,
    )
  ).json()

  let { snippet, contentDetails, statistics } = channelData.items[0]
  resData.snippet = snippet
  resData.statistics = statistics
  resData.uploadedVideos = contentDetails.relatedPlaylists.uploads
  const uploadedVideosPlaylistId = resData.uploadedVideos

  const uploadedVideosPart = (
    await fetch(
      `https://youtube.googleapis.com/youtube/v3/playlistItems?part=ContentDetails&playlistId=${uploadedVideosPlaylistId}&maxResults=50&key=${apiKey}`,
    )
  ).json()

  let { nextPageToken, items } = uploadedVideosPart
  videoData.push(...items.map((v) => v.contentDetails.videoId))
  await fetchWithToken(nextPageToken)
}

MainMethod()

您的编辑:

const fetchWithToken = (playlistId, nextPageToken) => {
  return new Promise((resolve) => {
    resolve(
      new Promise(async (res) => {
        const uploadedVideosTokenPart = (
          await fetch(
            `https://youtube.googleapis.com/youtube/v3/playlistItems?part=ContentDetails&playlistId=${playlistId}&pageToken=${nextPageToken}&maxResults=50&key=${apiKey}`,
          )
        ).json()

        let { items } = uploadedVideosTokenPart
        videoData.push(...items.map((v) => v.contentDetails.videoId))
        pageToken = uploadedVideosTokenPart.nextPageToken
          ? uploadedVideosTokenPart.nextPageToken
          : ''

        if (pageToken) {
          res(fetchWithToken(playlistId, pageToken))
        } else {
          res(new Promise((r) => r(true)))
        }
      }),
    )
  })
}
于 2020-12-22T17:21:51.403 回答