一尘不染

递归承诺不返回

node.js

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

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

我注意到您好永远不会返回,因为我怀疑我在递归调用上创建了多个promise,但是我不确定如何从中返回。

如何返回每个递归创建的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

阅读 250

收藏
2020-07-07

共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输入查询产品的功能

// fakes used for demonstration below
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是一个熟悉的异步函数,它返回结果的承诺。我们链接一个.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
    )

完整程序演示

// 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。这些问答可以帮助区分两者。

2020-07-07