Recursion call async func with promises gets Possi

2020-05-01 06:18发布

const PAGESIZE = 1000;
const DEFAULTLINK = `${URL}/stuff?pageSize=${PAGESIZE}&apiKey=${APIKEY}`;

export const getAllStuff = (initialLink = DEFAULTLINK) => {
  let allStuff = {};
  return getSuffPage(initialLink)
    .then(stuff => {
      allStuff = stuff;
      if (stuff.next) {
        return getAllStuff(stuff.next)
          .then(nextStuff => {
            allStuff = Object.assign({}, stuff, nextStuff);
            return allStuff;
          });
      } else {
        return allStuff;
      }
    });
};

const getSuffPage = nextPageLink => {
  fetch(nextPageLink).then(res => {
    return res.json();
  });
};

Calling getAllStuff throws:

Possible Unhandled Promise Rejection (id: 0): TypeError: Cannot read property 'then' of undefined TypeError: Cannot read property 'then' of undefined at getAllStuff

I think it is usually because I do not return from a promise then or something but where don't I?

1条回答
Emotional °昔
2楼-- · 2020-05-01 07:04

I've been working with anamorphisms or unfold in JavaScript lately and I thought I might share them with you using your program as a context to learn them in

const getAllStuff = async (initUrl = '/0') =>
  asyncUnfold
    ( async (next, done, stuff) =>
        stuff.next
          ? next (stuff, await get (stuff.next))
          : done (stuff)
    , await get (initUrl)
    )

const get = async (url = '') =>
  fetch (url) .then (res => res.json ())

To demonstrate that this works, we introduce a fake fetch and database DB with a fake delay of 250ms per request

const fetch = (url = '') =>
  Promise.resolve ({ json: () => DB[url] }) .then (delay)

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

const DB = 
  { '/0': { a: 1, next: '/1' }
  , '/1': { b: 2, next: '/2' }
  , '/2': { c: 3, d: 4, next: '/3' }
  , '/3': { e: 5 }
  }

Now we just run our program like this

getAllStuff () .then (console.log, console.error)

// [ { a: 1, next: '/1' }
// , { b: 2, next: '/2' }
// , { c: 3, d: 4, next: '/3' }
// , { e: 5 }
// ]

And finally, here's asyncUnfold

const asyncUnfold = async (f, init) =>
  f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
    , async (x) => [ x ]
    , init
    )

Program demonstration 1

const asyncUnfold = async (f, init) =>
  f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
    , async (x) => [ x ]
    , init
    )

const getAllStuff = async (initUrl = '/0') =>
  asyncUnfold
    ( async (next, done, stuff) =>
        stuff.next
          ? next (stuff, await get (stuff.next))
          : done (stuff)
    , await get (initUrl)
    )

const get = async (url = '') =>
  fetch (url).then (res => res.json ())

const fetch = (url = '') =>
  Promise.resolve ({ json: () => DB[url] }) .then (delay)

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

const DB = 
  { '/0': { a: 1, next: '/1' }
  , '/1': { b: 2, next: '/2' }
  , '/2': { c: 3, d: 4, next: '/3' }
  , '/3': { e: 5 }
  }

getAllStuff () .then (console.log, console.error)

// [ { a: 1, next: '/1' }
// , { b: 2, next: '/2' }
// , { c: 3, d: 4, next: '/3' }
// , { e: 5 }
// ]

Now say you wanted to collapse the result into a single object, we could do so with a reduce – this is closer to what your original program does. Note how the next property honors the last value when a key collision happens

getAllStuff ()
  .then (res => res.reduce ((x, y) => Object.assign (x, y), {}))
  .then (console.log, console.error)

// { a: 1, next: '/3', b: 2, c: 3, d: 4, e: 5 }

If you're sharp, you'll see that asyncUnfold could be changed to output our object directly. I chose to output an array because the sequence of the unfold result is generally important. If you're thinking about this from a type perspective, each foldable type's fold has an isomorphic unfold.

Below we rename asyncUnfold to asyncUnfoldArray and introduce asyncUnfoldObject. Now we see that the direct result is achievable without the intermediate reduce step

const asyncUnfold = async (f, init) =>
const asyncUnfoldArray = async (f, init) =>
  f ( async (x, acc) => [ x, ...await asyncUnfoldArray (f, acc) ]
    , async (x) => [ x ]
    , init
    )

const asyncUnfoldObject = async (f, init) =>
  f ( async (x, acc) => ({ ...x, ...await asyncUnfoldObject (f, acc) })
    , async (x) => x
    , init
    )

const getAllStuff = async (initUrl = '/0') =>
  asyncUnfold
  asyncUnfoldObject
    ( async (next, done, stuff) =>
    , ...
    )

getAllStuff ()
  .then (res => res.reduce ((x, y) => Object.assign (x, y), {}))
  .then (console.log, console.error)

// { a: 1, next: '/3', b: 2, c: 3, d: 4, e: 5 }

But having functions with names like asyncUnfoldArray and asyncUnfoldObject is completely unacceptable, you'll say - and I'll agree. The entire process can be made generic by supplying a type t as an argument

const asyncUnfold = async (t, f, init) =>
  f ( async (x, acc) => t.concat (t.of (x), await asyncUnfold (t, f, acc))
    , async (x) => t.of (x)
    , init
    )

const getAllStuff = async (initUrl = '/0') =>
  asyncUnfoldObject
  asyncUnfold
    ( Object
    , ...
    , ...
    )

getAllStuff () .then (console.log, console.error)

// { a: 1, next: '/3', b: 2, c: 3, d: 4, e: 5 }

Now if we want to build an array instead, just pass Array instead of Object

const getAllStuff = async (initUrl = '/0') =>
  asyncUnfold
    ( Array
    , ...
    , ...
    )

getAllStuff () .then (console.log, console.error)

// [ { a: 1, next: '/1' }
// , { b: 2, next: '/2' }
// , { c: 3, d: 4, next: '/3' }
// , { e: 5 }
// ]

Of course we have to concede JavaScript's deficiency of a functional language at this point, as it does not provide consistent interfaces for even its own native types. That's OK, they're pretty easy to add!

Array.of = x =>
  [ x ]

Array.concat = (x, y) =>
  [ ...x, ...y ]

Object.of = x =>
  Object (x)

Object.concat = (x, y) =>
  ({ ...x, ...y })

Program demonstration 2

Array.of = x =>
  [ x ]
  
Array.concat = (x, y) =>
  [ ...x, ...y ]
  
Object.of = x =>
  Object (x)

Object.concat = (x, y) =>
  ({ ...x, ...y })

const asyncUnfold = async (t, f, init) =>
  f ( async (x, acc) => t.concat (t.of (x), await asyncUnfold (t, f, acc))
    , async (x) => t.of (x)
    , init
    )

const getAllStuff = async (initUrl = '/0') =>
  asyncUnfold
    ( Object // <-- change this to Array for for array result
    , async (next, done, stuff) =>
        stuff.next
          ? next (stuff, await get (stuff.next))
          : done (stuff)
    , await get (initUrl)
    )

const get = async (url = '') =>
  fetch (url).then (res => res.json ())

const fetch = (url = '') =>
  Promise.resolve ({ json: () => DB[url] }) .then (delay)

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

const DB = 
  { '/0': { a: 1, next: '/1' }
  , '/1': { b: 2, next: '/2' }
  , '/2': { c: 3, d: 4, next: '/3' }
  , '/3': { e: 5 }
  }

getAllStuff () .then (console.log, console.error)

// { a: 1, next: '/3', b: 2, c: 3, d: 4, e: 5 }

Finally, if you're fussing about touching properties on the native Array or Object, you can skip that and instead pass a generic descriptor in directly

const getAllStuff = async (initUrl = '/0') =>
  asyncUnfold
    ( { of: x => [ x ], concat: (x, y) => [ ...x, ...y ] } 
    , ...
    )

getAllStuff () .then (console.log, console.error)

// [ { a: 1, next: '/1' }
// , { b: 2, next: '/2' }
// , { c: 3, d: 4, next: '/3' }
// , { e: 5 }
// ]
查看更多
登录 后发表回答