【问题标题】:Recursive Promises Not Returning递归承诺不返回
【发布时间】:2018-10-11 14:22:08
【问题描述】:

我有一个这样的递归函数

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

【问题讨论】:

  • 您需要在 else{} 块中添加 resolve/reject
  • 您确定没有引发异常吗?为什么不也添加一个catch 来查看它?
  • 我尝试在递归调用之后添加一个解析,但意识到随着另一个递归调用的继续,外部承诺将解析导致 hello 在递归函数真正完成之前被调用
  • else 块中,尝试用missingItemsPromise().then(resolve) 替换最后两行。

标签: javascript node.js recursion promise


【解决方案1】:

递归是一种函数式遗产,因此将其与函数式风格一起使用会产生最佳效果。这意味着编写函数来接受和操作其输入(而不是依赖于外部状态)和返回值(而不是依赖于突变或副作用)。

另一方面,您的程序调用不带参数的函数,使用外部状态 missingItemsproductsproductsLengthtotalpage 并使用像 page++ 这样的突变和像 @987654333 这样的重新分配@、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 helper

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 是一个熟悉的异步函数,它返回一个 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
    )

完整程序演示

// 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 可能会有所帮助。这些问答可能有助于区分两者。

【讨论】:

  • 要消化的内容很多,但感谢您的详尽回答!
  • 我很乐意为您提供帮助。如果您遇到任何问题,请告诉我:D
【解决方案2】:

您缺少 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
                });
            });
        } 
    });
};

【讨论】:

  • 没有变化 :\ 即使我回到那一行 hello 也永远不会打印出来
猜你喜欢
  • 2017-04-02
  • 2019-05-25
  • 1970-01-01
  • 1970-01-01
  • 2018-11-07
  • 2017-05-09
  • 2018-07-02
  • 2018-01-28
  • 1970-01-01
相关资源
最近更新 更多