节点获取循环太慢

Node fetch loop too slow

我有一个 API js 文件,我用 POST 方法调用它,传入一个对象数组,每个对象包含一个站点 url(大约 26 个对象或 urls) 作为正文,并使用下面的代码循环遍历此数组 (sites) ,检查每个对象是否 url returns 通过添加到url "/items.json" ,如果是这样,将 json 内容推送到另一个最终数组 siteLists 中,我将其作为响应发回。

问题只持续了 26 url 秒,这个 API 调用需要超过 5 秒 才能完成,我是不是做错了还是 fetch 在 Node.js 中的工作方式?

const sites 内容如下:

[{label: "JonLabel", name: "Jon", url: "jonurl.com"},{...},{...}]

代码是:

export default async (req, res) => {

    if (req.method === 'POST') {
        const body = JSON.parse(req.body)

        const sites = body.list  // this content shown above
        var siteLists = []
        
        if (sites?.length > 0){
            
            var b=0, idd=0
            while (b < sites.length){

                let url = sites?.[b]?.url

                if (url){

                    let jurl = `${url}/items.json`

                    try {
                        let fUrl = await fetch(jurl)
                        let siteData = await fUrl.json()
                
                        if (siteData){
                            let items = []
                            let label = sites?.[b]?.label || ""
                            let name = sites?.[b]?.name || ""
                            let base = siteData?.items
                        
                            if(base){
                                var c = 0
                                while (c < base.length){
                                    let img = base[c].images[0].url
                                    let titl = base[c].title

                                    let obj = {
                                        url: url,
                                        img: img,
                                        title: titl
                                    }
                                    items.push(obj)
                                    c++
                                }
                                let object = {
                                    id: idd,
                                    name: name,
                                    label: label,
                                    items: items
                                }
                                
                                siteLists.push(object)
                                idd++
                            }

                        }
                        
                    }catch(err){
                        //console.log(err)
                    }
                }
            
            b++
        }

        res.send({ sites: siteLists })
    }
res.end()
}

编辑:(解决方案?) 因此,似乎下面建议的带有承诺并标记为解决方案的代码在更快的意义上起作用,有趣的是它仍然需要超过 5 秒才能加载并且仍然会抛出 Failed to load resource: the server responded with a status of 504 (Gateway Time-out) 错误,因为 Vercel ,托管应用程序的无服务器函数的最大超时时间为 5 秒,因此永远不会在响应中加载内容。在本地,我没有超时限制的加载速度明显更快,但令我惊讶的是,这样的查询需要这么长时间才能完成,而这应该是毫秒的问题。

我在这里看到的最大问题是,在循环开始下一个 fetch 请求之前,您似乎 await 正在等待一个 fetch 完成,实际上 运行他们 连续 。如果您将脚本重写为 运行 同时 并行 ,您可以将每个请求按顺序推送到 Promise.all 中,然后在结果 return.

可以这样想——如果每个请求都需要一秒钟才能完成,而您有 26 个请求,并且您等待一个完成后再开始下一个,则总共需要 26 秒。但是,如果你 运行 他们每个人都在一起,如果他们每个人只需要一秒钟就可以完成整个事情,那么总共只需要一秒钟。

伪代码中的一个例子--

你想改变这个:

const urls = ['url1', 'url2', 'url3'];

for (let url of urls) {
    const result = await fetch(url);
    process(result)
}

...变成这样:

const urls = ['url1', 'url2', 'url3'];

const requests = [];

for (let url of urls) {
    requests.push(fetch(url));
}

Promise.all(requests)
    .then(
        (results) => results.forEach(
            (result) => process(result)
        )
    );

虽然 await 是一种很好的糖,但有时最好还是坚持使用 then

export default async (req, res) => {
    if (req.method === 'POST') {
        const body = JSON.parse(req.body)
        const sites = body.list  // this content shown above
        const siteListsPromises = []
        if (sites?.length > 0){
            var b=0
            while (b < sites.length){
                let url = sites?.[b]?.url
                if (url) {
                    let jurl = `${url}/items.json`
                    // #1
                    const promise = fetch(jurl)
                        // #2
                        .then(async (fUrl) => {
                            let siteData = await fUrl.json()
                            if (siteData){
                                ...
                                return {
                                    // #3
                                    id: -1,
                                    name: name,
                                    label: label,
                                    items: items
                                }
                            }
                        })
                        // #4
                        .catch(err => {
                            // console.log(err)
                        })
                    siteListsPromises.push(promise)
                }
                b++
            }
        }
        // #5
        const siteLists = (await Promise.all(siteListsPromises))
            // #6
            .filter(el => el !== undefined)
            // #7
            .map((el, i) => ({ id: i, ...el }))
        res.send({ sites: siteLists })
    }
    res.end()
}

在代码段中查找 // #N 条评论。

  1. 不要await请求完成。而是迭代 sites 并一次发送所有请求
  2. fetchthen 之后链 json()siteData 处理。如果您对 siteData 的处理需要更多的计算量,那么这样做会更有意义,而不是仅在所有承诺都解决后才执行所有操作。
  3. 如果您(或您团队中的某个人)在理解闭包方面遇到一些困难,请不要费心在循环中设置 idsiteData 个元素。我不会深入探讨这个问题,但会进一步解决它。
  4. 使用 .catch() 而不是 try{}catch(){}。因为没有 await 它不会工作。
  5. await 包含 Promise.all()
  6. 的所有请求的结果
  7. 过滤掉那些 siteData 是假的
  8. 最后设置 id 字段。