递归承诺不返回

时间:2018-05-01 14:27:38

标签: javascript node.js recursion promise

我有一个像这样的递归函数

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');   
});

我注意到hello永远不会返回,因为我怀疑我在递归调用中创建了多个承诺,但我不确定如何退出它。

如何返回每个递归创建的promise?

编辑:

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 :(得分:1)

递归是一种功能性遗产,因此将其与功能风格结合使用可获得最佳效果。这意味着编写接受和操作其输入的函数(而不是依赖于外部状态)并返回值(而不是依赖于突变或副作用)。

另一方面,您的程序调用不带参数的函数,使用外部状态missingItemsproductsproductsLengthtotalpage并使用像page++这样的突变以及products = ...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功能即可使用offsetlimit输入查询产品

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
    ( ... )
    .then (concatAll)

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
    )

完整的程序演示

&#13;
&#13;
// 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 ]
&#13;
&#13;
&#13;

我已回答有关递归和承诺的其他问题

异步和递归是不同的概念。如果您正在与asyncUnfold进行斗争,那么首先了解其同步对应unfold可能会有所帮助。这些Q&amp; A可能有助于区分这两者。

答案 1 :(得分:0)

你缺少else条件中的return语句

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
                });
            });
        } 
    });
};