how to pipe functions, when a promise in the promi

2019-02-19 20:50发布

问题:

i'm trying to compose some functions together:

compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

checkAuthorization returns a promise that check if a user is authorized. buildParams receives someRequestData, and pipes the result to searchItem.

    checkAuthorization()
      .then(() => {
            compose(
                searchItem,
                buildParams
            )(someRequestData)
    }, (e) => {
       handleError(e)
    })

I think it's OK, but I wish to have a more elegant look for readability, something like:

    compose(
      searchItem,
      checkAuthorization
      buildParams
    )(someRequestData)

so what will happen is: 1) build params 2) checkAuth 3) search item

Any suggestions?

回答1:

No, that's not possible, since checkAuthorisation does not receive and pass through the params. And even if you would rewrite it to do that, it still would be weird and a reader would assume that you're building the params whose authorisation should be checked. So don't do that - you have a non-linear flow, and trying to force it into some linear composition is no good.

Btw, I would recommend to avoid compose when you're calling the function immediately anyway:

checkAuthorization().then(() =>
  searchItem(buildParams(someRequestData))
, e =>
   handleError(e)
);

or maybe

checkAuthorization().then( compose(searchItem, buildParams, ()=>someRequestData)
                         , handleError ); //                ^^^^ "const"


回答2:

Here's a composer to handle both sync functions and Promises. Looks like it works correctly maintaining the order:

// Async pipe try. Pass functions left to right
const pipePromises = (...fns) => x => fns.reduce((p, fn) => p.then(fn), Promise.resolve(x));
  
// functions for the test
const me = x => new Promise(function(resolve, reject) {
  setTimeout(() => resolve(x), 10)
})

const double = x => new Promise(function(resolve, reject) {
  setTimeout(() => resolve(x * 2), 30)
})

const inc = x => new Promise(function(resolve, reject) {
  setTimeout(() => resolve(x + 1), 400)
})

const log = x => { console.log('log: ', x); return x }

const syncTriple = x => x * 3; // sync function

// let's call our chain
pipePromises( 
  me, log, // 3
  double, log, // 6
  syncTriple, log, // 18 -- SYNC
  inc, log, // 19
  double, log, // 38
  inc, log, // 39
  syncTriple, log, // 117 -- SYNC
  inc, log // 118
)(3) // 3



回答3:

I just made an npm module to handle elegant Promise composition. It's still in early stage, but you're welcome to check out the code and change it as it fits your needs and standards.

Basically it offers two methods which might meet your needs:

Combine

With Promise.combine({...}) you can combine several Promises by providing an object with a series of functions returning Promises and accepting the result of previous ones as input like this:

Promise.combine({
  item: () => searchItem,
  auth: ({item}) => checkAuth,
  params: ({item, auth}) => buildParams
}).then(({item, auth, params}) => {
  // here you can do what you need
})

Reduce

With Promise.reduce([...]) you can chain Promises in an array of functions returning Promises and accepting as input the output of the previously executed Promise:

Promise.reduce([
  () => searchItem,
  (item) => checkAuth,
  (auth) => buildParams
]).then((params) => {
  // here you can do what you need
})

Notice in this case you won't have access to item in the .then() function, but you could always compose the result of the checkAuth Promise in order to pass the item downstream as well:

Promise.reduce([
  () => searchItem,
  (item) => checkAuth.then((auth) => {
      return {auth, item}
  }),
  ({auth, item}) => buildParams.then((params) => {
      return {params, item}
  }),
]).then(({params, item}) => {
  // here you can do what you need
})

Input

You can also add some input data from the request like this:

Promise.reduce([
  (requestData) => searchItem,
  (item) => checkAuth,
  (auth) => buildParams
], requestData).then((params) => {
  // here you can do what you need
})

See I passed the requestData as second parameter of Promise.reduce([...], requestData) and it gets passed as parameter to the first function.

Here you can see the functions code. Hope this helps.