Обещания JavaScript: рекурсивное построение цепочки обещаний с обходом в ширину

Обещания нативного Javascript ES5/ES6

Я пытаюсь импортировать данные, которые имеют рекурсивные отношения, и поскольку база данных (mongodb) назначает идентификаторы, родитель должен быть загружен (асинхронно), прежде чем его дети могут быть загружены (также асинхронно).

Например, Задача B в этом списке задач.

Задача A — Некоторый процесс

Задача B — рекурсивная асинхронная загрузка (обход в первую очередь)

Задача C — зависит от задачи B

Обратите внимание, что задача C не может быть запущена до тех пор, пока не будет завершена задача B. Я предполагаю, что необходимо построить цепочку обещаний, которая не завершится, пока она не будет выполнена.

Предположим, что строящаяся цепочка будет выглядеть примерно так: (у дерева только 1 голова)

promiseParent.then(Promise.all(childrenPromises.then(Promise.all(grandChildrenPromsies.then(....)))))

Я предполагаю, что это будет проходить как очередь в ширину (предпочтительно, я хотел бы попытаться избежать использования структуры данных очереди, если это возможно)

Я нашел, что это было трудно взломать. Любые предложения или решения?


person Nick Pineda    schedule 09.03.2016    source источник
comment
Не могли бы вы просто привязать его? taskA.then(function() { return taskB() }).then(...   -  person adeneo    schedule 09.03.2016
comment
Задача Б — вызов. Задача B должна рекурсивно построить цепочку обещаний, которые должны быть выполнены до вызова задачи C. Задача A не имеет значения (просто показывает, что что-то происходит до вызова B. @BlakesSeven   -  person Nick Pineda    schedule 09.03.2016
comment
Я не делал комментарий. @аденео сделал. Все, что я сделал, это удалил тег mongodb (предположительно, автоматически вставленный волшебным редактором вопросов stackexchange!), Поскольку он действительно не относится конкретно к проблеме, связанной с обещаниями.   -  person Blakes Seven    schedule 09.03.2016
comment
Заметили, но слишком поздно, извините, Блейк, и да, я думаю, это не слишком актуально для сообщества Mongodb.   -  person Nick Pineda    schedule 09.03.2016
comment
Я хочу сказать, вам не нужно их вкладывать друг в друга, вы можете связать их   -  person adeneo    schedule 09.03.2016
comment
Однако целью является объединение задач внутри задачи B. Я надеюсь, что люди не сбиты с толку тем, что задача А находится в этом примере.   -  person Nick Pineda    schedule 09.03.2016
comment
Я не понимаю, как вы могли бы написать обход в ширину без очереди. Зацикливание до тех пор, пока оно не станет пустым, тривиально.   -  person Bergi    schedule 09.03.2016
comment
@JesseKernaghan Я не мог понять, как вернуть обещание детей до того, как обещание их родителей было выполнено. Потому что я не мог вызвать создание дочерних элементов, потому что они сохранили ссылку на своего родителя, имея свойство, в котором хранится parentID.   -  person Nick Pineda    schedule 09.03.2016
comment
@adeneo - близко к этому, но это должен быть рекурсивный вызов, который загружает дочерние элементы после завершения родителя.   -  person Nick Pineda    schedule 09.03.2016
comment
@Bergi, если очередь необходима, я буду ее использовать - я просто надеялся, что цепочка обещаний и рекурсивная остановка после попадания в дочерний элемент без дочерних элементов могут работать без структуры данных очереди.   -  person Nick Pineda    schedule 09.03.2016
comment
@NickPineda: очередь необходима для обхода в ширину. Если у вас все в порядке с одновременной загрузкой (параллельным обходом), вам это не нужно. См. эти ссылки для примеров того, как это сделать.   -  person Bergi    schedule 09.03.2016
comment
Вы заранее знаете количество детей? Если да, то вам не нужно рекурсивно.   -  person jib    schedule 09.03.2016


Ответы (1)


Цепочки обещаний можно расширять динамически, добавляя новые ссылки в любой точке цепочки, просто возвращая обещание из любого обработчика выполнения .then.

Скажем, каждая задача разрешается массивом своих дочерних элементов. Если дочерние элементы могут обрабатываться параллельно, то:

promiseParent
.then(children => Promise.all(children.map(child => loadChild(child))))
.then(grandchildren => Promise.all(grandchildren.map(child => loadChild(child))))

стоит сделать. Если дочерние элементы должны обрабатываться последовательно, то:

let sequence = (a, f) => a.reduce((p, c) => p.then(() => f(c)), Promise.resolve());

promiseParent
.then(kids => sequence(kids, kid => loadChild(kid)).then(() => nextGen())
.then(gkds => sequence(gkds, kid => loadChild(kid)).then(() => nextGen())

сделает это (я упрощаю, предполагая, что nextGen знает, как вернуть следующее поколение).

Если количество дочерних элементов должно быть обнаружено рекурсивно, то:

let loadChildrenRecursively = child => loadChild(child)
  .then(nextChild => nextChild && loadChildrenRecursively(nextChild));

promiseParent
.then(firstChild => loadChildrenRecursively(firstChild)).then(() => nextGen())
.then(firstGchild => loadChildrenRecursively(firstGchild)).then(() => nextGen())

должен сделать это.

Чтобы обобщить это на N уровней, выберите любой подход выше, скажем, параллельно, и повторите его:

let doGeneration = generation =>
  Promise.all(generation.map(child => loadChild(child))))
  .then(offsprings => offsprings && doGeneration(offsprings))

promiseParent.then(children => doGeneration(children));

Таким образом, вы всегда можете расширить, если есть что сделать, разрешив обещание с другим обещанием (это то, что вы неявно сделать, возвращая новое обещание из обработчика выполнения .then).

person jib    schedule 09.03.2016
comment
Количество уровней также кажется динамическим. - person Bergi; 09.03.2016
comment
@Bergi Я обновил ответ, чтобы охватить также динамическое количество уровней. - person jib; 09.03.2016
comment
@джиб святой!! Ничего себе, этот ответ выглядит гладким! Да, я надеялся на динамический ответ, чтобы обеспечить гибкость в отношении глубины ветки (так что спасибо Берги за добавление этого туда) и последовательности, поскольку дочернему элементу потребуется новый родительский идентификатор, прежде чем его тоже можно будет импортировать. - person Nick Pineda; 09.03.2016
comment
Спасибо! Между прочим, параллельно просто означает параллельно с другими дочерними элементами, поэтому все три решения предположительно будут иметь доступ к родительскому идентификатору, потому что все они ждут завершения всех родительских процессов перед запуском. - Кроме того, если родительский идентификатор - это единственное, чего вы ждете, то вам может не понадобиться обход в ширину, поскольку вам не нужно будет ждать родителей-близнецов, я не думаю , только собственный родитель? Если вы хотите оптимизировать, то есть. - person jib; 09.03.2016