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