Как упростить JS-код с помощью вложенных сопрограмм и промисов?

У меня есть этот фрагмент рабочего кода, который я действительно хочу упростить. Он предназначен для Node.js v6.0 с пакетами bluebird & co. Это типичная ситуация, и я хотел бы, чтобы код был компактным и простым для понимания, чтобы я мог повторно использовать его в других проектах.

Функция возвращает один контрольный список с несколькими категориями элементов (1:m), и каждая категория содержит несколько элементов (1:m:n). Итак, это объект с двумя вложенными уровнями:

Root: Checklist
    Level 1: Checklist Item Group(s)
        Level 2: Checklist Item(s)

Корневой уровень (1 контрольный список) и один уровень ниже (категории элементов контрольного списка) могут быть реализованы в функции генератора с помощью yield.

Уровень 1: n: m не может использовать «доходность», когда я бы использовал, например. Promise.mapSeries() вместо checklist.__node__ChecklistItemGroups, потому что «доходность» поддерживается только в основной функции генератора, а не во внутренней функции карты. Поэтому я вернулся к функции Promise.each() и конструкции Promise.then().

function getGraphChecklistById(pId) {
    return co(function *() {
        let checklist = yield getChecklistById(pId); // Returns a promise.
        checklist.__node__ChecklistItemGroups = yield fetchChecklistItemGroupsByChecklistId(pId); // Returns a promise.
        return checklist;
    })
        .then(function (checklist) {
            return Promise.each(
                checklist.__node__ChecklistItemGroups,
                function (pItem) {
                    return fetchChecklistItemsByXrefId(pItem.xchlclig_id).then(function (result) {  // fetchChecklistItemsByXrefId() returns a promise.
                        pItem.__node__ChecklistItems = result;
                    });
                })
                .then(function (unused) {
                    return checklist;
                });
        })
}

Это пример результата:

result: {
    "checklist-field-1": 1,
    "checklist-field-2": 1,
    "__node__ChecklistItemGroups": [
        {
            "checklist-group-field-1": 1,
            "checklist-group-field-2": 1,
            "__node__ChecklistItems": [
                {
                    "checklist-item-field-1": 1,
                    "checklist-item-field-2": 1,
                },
                {
                    "checklist-item-field-1": 2,
                    "checklist-item-field-2": 2,
                }
            ]
        },
        {
            "checklist-group-field-1": 2,
            "checklist-group-field-2": 2,
            "__node__ChecklistItems": [
                {
                    "checklist-item-field-1": 1,
                    "checklist-item-field-2": 1,
                },
                {
                    "checklist-item-field-1": 2,
                    "checklist-item-field-2": 2,
                }
            ]
        }
    ]
}

Любые предложения от эксперта Promise/Coroutines? Спасибо за ваше время.


person Rolf    schedule 02.06.2016    source источник


Ответы (1)


Я бы написал так - без Promise.each, а простой цикл:

let getGraphChecklistById = Promise.coroutine(function*(pId) {
//                          ^^^^^^^^^^^^^^^^^ Don't put co(…) in a function, wrap directly
    let checklist = yield getChecklistById(pId);
    checklist.__node__ChecklistItemGroups = yield fetchChecklistItemGroupsByChecklistId(pId); // Returns a promise.
    for (let pItem of checklist.__node__ChecklistItemGroups) {
//  ^^^ iteration in series like Promise.each does it
        let result = yield fetchChecklistItemsByXrefId(pItem.xchlclig_id);
//                   ^^^^^ You can use yield here as it's not an extra function
        pItem.__node__ChecklistItems = result;
    }
    return checklist;
});

Используете ли вы Bluebird Promise.coroutine или co.wrap здесь не имеет большого значения.

И если вы хотите перебирать группы параллельно, вы все равно можете использовать yield в функции обратного вызова, если сделаете это генератором:

let getGraphChecklistById = Promise.coroutine(function*(pId) {
    let checklist = yield getChecklistById(pId); 
    checklist.__node__ChecklistItemGroups = yield fetchChecklistItemGroupsByChecklistId(pId); // Returns a promise.
    yield Promise.map(
        checklist.__node__ChecklistItemGroups,
        Promise.coroutine(function*(pItem) {
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^ pass this as the promise-returning callback function
            let result = yield fetchChecklistItemsByXrefId(pItem.xchlclig_id);
            pItem.__node__ChecklistItems = result;
        })
    );
    return checklist;
});
person Bergi    schedule 02.06.2016
comment
Спасибо, bergi, за то, что сократили 50% #строк кода :) Подсказка действительно в том, что для итерации for-of не нужна внутренняя функция. - person Rolf; 02.06.2016
comment
Я применил рекомендуемые изменения и продолжал использовать пакет co. Окончательный результат gist.github.com/pantaluna/312627ee61742850eb679c7d5125e20d - person Rolf; 02.06.2016
comment
@Rolf: я все еще думаю, что вам лучше использовать co.wrap (и вы можете сэкономить еще две строки). - person Bergi; 02.06.2016
comment
Хорошо сделал это. gist.github.com/pantaluna/6a3cb8e0a96575bf3302931528e022a4 Я также удалил зависимость от 'co'. Мне трудно понять, что я могу передать параметр и передать его функции-генератору (pId). пусть getGraphChecklistById = Promise.coroutine (функция * (pId) { - person Rolf; 02.06.2016