快速响应,与递归函数异步

Express response, async with recursive function

我正在尝试从 Cloudinary 获取所有照片,但每次调用限制为 500 个元素。

调用中有一个名为 next_cursor 的属性,您可以将其用作分页。

我是递归执行的,但是当我尝试在 Express 中发送响应超出范围时,关于如何使用递归函数的结果进行响应有什么想法吗?

谢谢!

const express = require("express");
const router = express.Router();
const cloudinary = require("cloudinary").v2;

const { cloudinarySettings } = require("../config/cloudinary_config");

router.get("/statistics", async (req, res) => {
    function list_resources(results, next_cursor = null) {
        cloudinary.api.resources(
            {
                resource_type: "image",
                mex_results: 500,
                next_cursor: next_cursor,
            },
            function (err, res) {
                console.log(err);
                res.resources.forEach(function (resource) {
                    results.push(resource);
                });

                if (res.next_cursor) {
                    list_resources(results, res.next_cursor);
                } else {
                    return res.status(200).json(results)
                }
            }
        );
    }

    const results = [];
    list_resources(results);
});

您需要将 list_resources 函数修改为 return 承诺,如下所示,然后使用 async-await 等待结果,然后再提交 res 返回。

router.get("/statistics", async (req, res) => {
    async function list_resources(results, next_cursor = null) {
        await new Promise((resolve) => {
            cloudinary.api.resources(
                {
                    resource_type: "image",
                    mex_results: 500,
                    next_cursor: next_cursor,
                },
                function (err, res) {
                    if (err) {
                        console.log(err);
                        resolve();
                        
                    } else {
                        res.resources.forEach(function (resource) {
                            results.push(resource);
                        });

                        if (res.next_cursor) {
                            list_resources(results, res.next_cursor).then(() => resolve());
                        } else {
                            resolve();
                        }
                    }
                    
                }
            );
        });
    }

    const results = [];
    await list_resources(results);
    return res.status(200).json(results);
});

我可能会以不同的方式分解问题,并制作一个更简单的递归函数,该函数 returns 结果而不是修改传入的数组。我还将函数移出 router.get 清洁回调。

所以它可能看起来像这样:

const listResources = async ({resource_type, max_results = 500, next_cursor = null} = {}) => {
  return new Promise((resolve, reject) => {
    cloudinary .api .resources (
      {resource_type, max_results, next_cursor}, 
      async (err, {resources, next_cursor}) => {
        try {
          if (err) {reject (err)}
          else resolve ([
            ...resources, 
            ...(next_cursor ? await listResources ({resource_type, max_results, next_cursor}) : [])
          ])
        } catch (err) {reject (err)}
      }
    )
  })
}

router.get('/statistics', async (req, res) => {
  try {
    const results = await listResources ({resource_type: 'image'})
    res .status (200) .json (results)
  } catch (err) {
    console .warn (err)
    res .status (404) // or 500, or whatever, based on error
  }
})

最有趣的部分是:

    resolve ([
      ...resources, 
      ...(next_cursor ? await listResources ({resource_type, max_results, next_cursor}) : [])
    ])

如果我们得到一个 next_cursor 值,我们会在解析它之前将使用它的递归调用的结果附加到我们当前的结果中。如果我们没有得到一个,我们追加一个空数组。

如果能够编写这个可以说更简单的 cloudinary 回调函数就好了:

    async (err, {resources, next_cursor}) => err 
      ? reject (err)
      : resolve ([
          ...resources, 
          ...(next_cursor ? await listResources ({resource_type, max_results, next_cursor}) : [])
        ])

避免 try-catchif-else 块。但是我找不到一种直接的方法来做到这一点并将错误传递回最初调用 listResources.

的函数

您可以在您的浏览器中使用此代码段中 cloudinaryrouter 的虚拟实现进行测试:

const listResources = async ({resource_type, max_results, next_cursor = null}) => {
  return new Promise((resolve, reject) => {
    cloudinary.api.resources(
      {resource_type, max_results, next_cursor}, 
      async (err, {resources, next_cursor}) => {
        try {
          if (err) {reject (err)}
          else resolve ([
            ...resources, 
            ...(next_cursor ? await listResources ({resource_type, max_results, next_cursor}) : [])
          ])
        } catch (err) {reject (err)}
      }
    )
  })
}

router.get('/statistics', async (req, res) => {
  try {
    const results = await listResources ({resource_type: 'image', max_results: 3})
    res .status (200) .json (results)
  } catch (err) {
    console .warn (err)
    res .status (500) // or whatever, based on error
  }
})
.as-console-wrapper {max-height: 100% !important; top: 0}
<script>
// Dummy implementations for testing:

const cloudinary = (() => {
  const vals = [{t: 'image', id: 1,  v: 'a'}, {t: 'image', id: 2,  v: 'b'}, {t: 'image', id: 3,  v: 'c'}, {t: 'pdf',   id: 4,  v: 'd'}, {t: 'image', id: 5,  v: 'e'}, {t: 'image', id: 6,  v: 'f'}, {t: 'video', id: 7,  v: 'g'}, {t: 'image', id: 8,  v: 'h'}, {t: 'image', id: 9,  v: 'i'}, {t: 'image', id: 10, v: 'j'}, {t: 'image', id: 11, v: 'k'}, {t: 'image', id: 12, v: 'l'}]; 
  const last = (xs) => xs.length ? xs[xs.length - 1] : undefined; 
  return {api: {
    resources: ({resource_type, max_results, next_cursor = 0} , fn) => {
      const res = vals .filter (({t, id}) => t == resource_type && id > next_cursor).slice(0, max_results)
//      fn (next_cursor == 3 ? 'houston, we have a problem' : null, {  // *** use to test an error condition ***
      fn (null, {
        resources: res, 
        ...(res.length > 0 && last(res).id < last(vals).id ? {next_cursor: last(res).id} : {})
      })
    }
  }}
})()

const router = {
  get: (path, fn) =>
    fn (
      {},
      {status: (n) => {
        console.log(`returning ${n} for request to ${path}`)
        return {
          json: val => console.log(`Body: \n${JSON.stringify(val, null, 4)}`)
        }
      }}
    )
}
</script>

如果您取消注释虚拟 cloudinary 代码中的替代行并注释前一行,您可以看到 cloudinary .api .resources 引发错误时的行为。