Recursive Promises Not Returning

后端 未结 2 1229
走了就别回头了
走了就别回头了 2020-12-02 02:43

I have a recursive function like so

function missingItemsPromise() {
    return new Promise(resolve => {
        if (missingItems == 0) {
            con         


        
相关标签:
2条回答
  • 2020-12-02 03:06

    you are missing the return statement inside the else condition

    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
                    });
                });
            } 
        });
    };
    
    0 讨论(0)
  • 2020-12-02 03:16

    Recursion is a functional heritage and so using it with functional style yields the best results. That means writing functions that accept and operate on their inputs (rather than relying on external state) and return values (rather than relying on mutation or side effects).

    Your program, on the other hand, calls functions without arguments, uses external state missingItems, products, productsLength, total, page and uses mutations like page++ and reassignments like products = ..., productsLength = ..., missingItems = .... We're gonna fix all of this!

    I'm just going to blast thru this and hope it sets you on the right track. If you're stuck at the end, I link some other answers which explain the techniques used here in greater detail.

    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
        )
    

    We introduce the getPage helper we used above

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

    Next, for the purposes of this demo, we introduce a fake getProducts function, and a fake DB where each product is simply a number. We also use delay to simulate real network delay.

    In your real program, you just need to provide a getProducts function that can query products using offset and limit inputs

    // 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
      ]
    

    Below we demonstrate running the program. getAllProducts is a familiar async function which returns a Promise of its result. We chain a .then call so we can see all of the product pages output in the console

    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 ]
    // ]
    

    Instead of grouping products by page, it'd be nice if we could return all the products in a single array. We can modify getAllProducts slightly to achieve this

    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 ]

    Lastly, we introduce asyncUnfold

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

    Full program demonstration

    // 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 ]

    Other questions I've answered about recursion and promises

    • Recursion call async func with promises gets Possible Unhandled Promise Rejection
    • Recursive JS function with setTimeout
    • Promise with recursion

    Asynchrony and recursion are separate concepts. If you're struggling with asyncUnfold, it might help to first understand its synchronous counterpart unfold. These Q&A's may help distinguish the two.

    • Functional way to create an array of numbers
    • Loop until... with Ramda
    • Node.js recursively list full path of files
    0 讨论(0)
提交回复
热议问题