Рекурсивные обещания не возвращаются - PullRequest
0 голосов
/ 01 мая 2018

У меня есть рекурсивная функция, как так

function missingItemsPromise() {
    return new Promise(resolve => {
        if (missingItems == 0) {
            console.log('resolves');
            console.log(products);
            return resolve();
        } else {
            page++;
            url = getUrl(id, page);
            http.get(url, function(xres) {
                xres.setEncoding('utf8');
                xres.on('data', function (xtraBody) {
                    console.log('calling');
                    var xtraJson = JSON.parse(xtraBody);
                    var xtraProducts = xtraJson['products'];
                    products = products.concat(xtraProducts);
                    productsLength = products.length;
                    missingItems = total - productsLength;
                    missingItemsPromise();
                });
            });
        } 
    });
};

и я использую его как

getInitial.
then(missingItemsPromise).
then(() => {
 console.log('hello');   
});

Я замечаю, что привет никогда не возвращается, потому что я подозреваю, что я создаю более одного обещания для рекурсивного вызова, но я не уверен, как выйти из него.

Как я могу вернуть каждое рекурсивно созданное обещание?

РЕДАКТИРОВАТЬ:

function missingItemsPromise() {
    return new Promise(resolve => {
        if (missingItems == 0) {
            console.log('resolves');
            return resolve();
        } else {
            page++;
            url = getUrl(id, page);
            http.get(url, function(xres) {
                xres.setEncoding('utf8');
                xres.on('data', function (xtraBody) {
                    console.log('calling');
                    var xtraJson = JSON.parse(xtraBody);
                    var xtraProducts = xtraJson['products'];
                    products = products.concat(xtraProducts);
                    productsLength = products.length;
                    missingItems = total - productsLength;
                    missingItemsPromise();
                    resolve();
                });
            });
        }
    });
};

Результаты в

calling
hello <----notice here that it's already resolving once the first call resolve 
is called
calling
calling
resolves

Ответы [ 2 ]

0 голосов
/ 01 мая 2018

Рекурсия - это функциональное наследие, поэтому использование ее в функциональном стиле дает наилучшие результаты. Это означает написание функций, которые принимают и обрабатывают свои входные данные (а не полагаются на внешнее состояние) и возвращают значения (вместо того, чтобы полагаться на мутацию или побочные эффекты).

С другой стороны, ваша программа вызывает функции без аргументов, использует внешнее состояние missingItems, products, productsLength, total, page и использует мутации, такие как page++, и переназначения, такие как * 1009. *, productsLength = ..., missingItems = .... Мы исправим все это!

Я просто собираюсь пробиться сквозь это и надеюсь, что это направит вас на правильный путь. Если вы застряли в конце, я привожу некоторые другие ответы, которые более подробно объясняют методы, используемые здесь.

const getAllProducts = async (page = 0) =>
  asyncUnfold
    ( async (next, done, [ res, nextPage ]) =>
      res.products.length === 0
          ? done ()
          : next ( res.products                               // value to add to output
                 , [ await getPage (nextPage), nextPage + 1 ] // next state
                 )
    , [ await getPage (page), page + 1 ] // initial state
    )

Мы представляем getPage помощник, который мы использовали выше

const getPage = async (page = 0, itemsPerPage = 5) =>
  getProducts (page * itemsPerPage, itemsPerPage)
    .then (res => res.json ())

Далее, для целей этой демонстрации, мы вводим фальшивую функцию getProducts и фальшивую DB, где каждый продукт представляет собой просто число. Мы также используем delay для имитации реальной задержки в сети.

В вашей реальной программе вам просто нужно предоставить функцию getProducts, которая может запрашивать продукты, используя offset и limit входные данные

const getProducts = (offset = 0, limit = 1) =>
  Promise.resolve
    ({ json: () =>
        ({ products: DB.slice (offset, offset + limit) })
    })
  .then (delay)

const delay = (x, ms = 250) =>
  new Promise (r => setTimeout (r, ms, x))

const DB = 
  [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  , 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
  , 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
  , 31, 32, 33
  ]

Ниже мы покажем, как работает программа. getAllProducts - это знакомая асинхронная функция, которая возвращает Promise своего результата. Мы объединяем вызов .then, чтобы видеть все страницы продукта, выводимые в консоли

getAllProducts () .then (console.log, console.error)
// ~2 seconds later
// [ [ 1, 2, 3, 4, 5 ]
// , [ 6, 7, 8, 9, 10 ]
// , [ 11, 12, 13, 14, 15 ]
// , [ 16, 17, 18, 19, 20 ]
// , [ 21, 22, 23, 24, 25 ]
// , [ 26, 27, 28, 29, 30 ]
// , [ 31, 32, 33 ]
// ]

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

const concat = (xs, ys) =>
  xs .concat (ys)

const concatAll = (arrays) =>
  arrays .reduce (concat, [])

const getAllProducts = async (page = 0) =>
  asyncUnfold
    ( ... )
    <b>.then (concatAll)</b>

getAllProducts () .then (console.log, console.error)
// ~2 seconds later
// [ 1, 2, 3, 4, 5, 6, 7, ..., 31, 32, 33 ]

Наконец, мы вводим asyncUnfold

const asyncUnfold = async (f, initState) =>
  f ( async (value, nextState) => [ value, ...await asyncUnfold (f, nextState) ]
    , async () => []
    , initState
    )

Полная демонстрация программы

// dependencies -------------------------------------------------
const asyncUnfold = async (f, initState) =>
  f ( async (value, nextState) => [ value, ...await asyncUnfold (f, nextState) ]
    , async () => []
    , initState
    )

const concat = (xs, ys) =>
  xs .concat (ys)
  
const concatAll = (arrays) =>
  arrays .reduce (concat, [])
  

// fakes --------------------------------------------------------
const getProducts = (offset = 0, limit = 1) =>
  Promise.resolve
    ({ json: () =>
        ({ products: DB.slice (offset, offset + limit) })
    })
  .then (delay)

const delay = (x, ms = 250) =>
  new Promise (r => setTimeout (r, ms, x))

const DB = 
  [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  , 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
  , 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
  , 31, 32, 33
  ]

// actual program
const getAllProducts = async (page = 0) =>
  asyncUnfold
    ( async (next, done, [ res, nextPage ]) =>
      res.products.length === 0
          ? done ()
          : next ( res.products
                 , [ await getPage (nextPage), nextPage + 1 ]
                 )
    , [ await getPage (page), page + 1 ]
    )
    .then (concatAll)
    
const getPage = async (page = 0, itemsPerPage = 5) =>
  getProducts (page * itemsPerPage, itemsPerPage)
    .then (res => res.json ())

// demo ---------------------------------------------------------
getAllProducts ()
  .then (console.log, console.error)

// ~2 seconds later
// [ 1, 2, 3, ..., 31, 32, 33 ]

Другие вопросы, которые я ответил о рекурсии и обещаниях

Асинхронность и рекурсия являются отдельными понятиями. Если вы боретесь с asyncUnfold, это может помочь сначала понять его синхронный аналог unfold. Эти вопросы и ответы могут помочь отличить их.

0 голосов
/ 01 мая 2018

вы пропускаете оператор возврата внутри условия else

function missingItemsPromise() {
    return new Promise(resolve => {
        if (missingItems == 0) {
            console.log('resolves');
            console.log(products);
            return resolve();
        } else {
            page++;
            url = getUrl(id, page);
            http.get(url, function(xres) {
                xres.setEncoding('utf8');
                xres.on('data', function (xtraBody) {
                    console.log('calling');
                    var xtraJson = JSON.parse(xtraBody);
                    var xtraProducts = xtraJson['products'];
                    products = products.concat(xtraProducts);
                    productsLength = products.length;
                    missingItems = total - productsLength;
                   return  missingItemsPromise(); //this is the line that changes
                });
            });
        } 
    });
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...