Recursion call async func with promises gets Possible Unhandled Promise Rejection

那年仲夏 提交于 2019-12-02 08:02:38

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 }
// ]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!