Экспресс-ответ, синхронизированный с рекурсивной функцией

#javascript #express #recursion #callback #cloudinary

#javascript #экспресс #рекурсия #обратный вызов #cloudinary

Вопрос:

Я пытаюсь извлечь все фотографии из 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);
});  

Ответ №1:

Вам нужно будет изменить вашу list_resources функцию, чтобы возвращать обещание следующим образом, а затем использовать 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);
});
  

Ответ №2:

Я мог бы немного иначе решить проблему и создать более простую рекурсивную функцию, которая возвращает результаты, а не изменяет переданный массив. Я бы также убрал функцию из 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-catch и if-else . Но я не могу найти простой способ сделать это и передать ошибку обратно в первоначально вызываемую функцию listResources .

Вы можете протестировать это в своем браузере с помощью фиктивных реализаций cloudinary и router в этом фрагменте:

 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 amp;amp; 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 amp;amp; 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 выдается ошибка.