3

Below I create a function called promiseRipple takes in a object literal whose values are functions. Each function can contain synchronous code or an asynchronous promise.

var _ = require('lodash')
var Promise = require('bluebird')

function promiseRipple (start, props) {
  props = (props) ? props : start
  start = (props) ? start : {}
  props = _.mapValues(props, function (prop, key) {
    prop.key = key
    return prop
  })
  return Promise.reduce(_.values(props), function (result, action) {
    if (typeof action !== 'function') throw new Error('property values must be functions')
    return Promise.resolve(action(start)).then(function (value) {
      start[action.key] = value
      return value
    })
  }, null)
  .then(function () {
    return start
  })
}

Here's the basic usage

promiseRipple({zero: 'zero'}, {
  'alpha': function (data) {
    return Promise.resolve(data.zero + ' alpha') // async -> 'zero alpha'
  },
  'beta': function (data) {
    return data.zero + ' beta' // -> 'zero beta'
  },
  'gamma': function (data) {
    return Promise.resolve(data.zero + ' gamma') // async -> 'zero gamma'
  },
  'delta': function (data) {
    return Promise.resolve(data.zero + data.alpha + ' delta') // async -> 'zerozero alpha delta'
  },
}).then(function (results){
  // results -> {
  //   zero: 'zero',
  //   alpha: 'zero alpha',
  //   beta: 'zero beta',
  //   gamma: 'zero gamma',
  //   delta: 'zerozero alpha delta' 
  // }
})

I wanted to add in some functionalty to make is so whenever data was returned within a function, the new data propeties would be extended to the existing.

promiseRipple({zero: 'zero'}, {
  'alpha': function (data) {
    return Promise.resolve(data.zero + ' alpha') // async -> 'zero alpha'
  },
  'beta': function (data) {
    data.foo = data.zero + ' foo' // -> 'zero foo'
    data.bar = data.zero + ' bar' // -> 'zero bar'
    return data
  },
  'gamma': function (data) {
    return Promise.resolve(data.zero + ' gamma') // async -> 'zero gamma'
  },
  'delta': function (data) {
    return Promise.resolve(data.zero + data.alpha + ' delta') // async -> 'zerozero alpha delta'
  },
}).then(function (results){
  // results -> {
  //   zero: 'zero',
  //   alpha: 'zero alpha',
  //   foo: 'zero foo',
  //   bar: 'zero bar',
  //   gamma: 'zero gamma',
  //   delta: 'zerozero alpha delta' 
  // }
})

I tried to make a custom object literal that would allow for me to detect if the returned value from the prop function was data or some new variable. However this does not work.

var _ = require('lodash')
var Promise = require('bluebird')

function Start (data) {
  if (data) return data
  return {}
}
Start.prototype = Object.prototype

function promiseRipple (start, props) {
  props = (props) ? props : start
  start = (props) ? new Start(start) : new Start()
  props = _.mapValues(props, function (prop, key) {
    prop.key = key
    return prop
  })
  return Promise.reduce(_.values(props), function (result, action) {
    if (typeof action !== 'function') throw new Error('property values must be functions')
    return Promise.resolve(action(start)).then(function (value) {
      console.log(value instanceof Start)
      if (value instanceof Start) {
        _.extend(start, value)
        return start
      } else {
        start[action.key] = value
        return value
      }
    })
  }, null)
  .then(function () {
    return start
  })
}

module.exports = promiseRipple

What is a good way to detect if the object returned is the same object that we started out with, without messing with the value of the object?

ThomasReggi
  • 55,053
  • 85
  • 237
  • 424
  • Nothing here is asynchronous. Is that just for demonstration purposes? If not, removing all that promises would make everything much simpler. – Amit Aug 21 '15 at 22:33
  • Question makes no sense. All the detail about data and promises is irrelevant to the actual question asked. If you don't trust a function not to mess with an object, then don't pass it. Pass a (deep) clone instead. – Roamer-1888 Aug 22 '15 at 01:08
  • Don't do this. Objects are inherently unordered. You cannot rely on any particular order in which the functions are supposed to be executed. – Bergi Aug 22 '15 at 21:03
  • @Bergi Inherently unordered? Or inconsiderately ordered? Yes at times you can't guarantee order. However for control flow I believe it's fine. Check out [async, does the same thing](https://github.com/caolan/async#seriestasks-callback). I added some code to be the alternative to a ripple above and it's disastrous. I believe this is a viable alternative when it comes to control flow & object creation. – ThomasReggi Aug 22 '15 at 21:43
  • @ThomasReggi: Yes, async.js has the very same problem. Yes, objects are inherently unordered, and this is especially important for control flow. For (much better) alternatives have a look at [How do I access previous promise results in a .then() chain?](http://stackoverflow.com/q/28250680/1048572), and Bluebird's `Promise.props` (in case you are really looking to create an object) – Bergi Aug 22 '15 at 22:02
  • @Bergi What are the real dangers of using the `promiseRipple` function as I authored it? It seems to be an alternative to using a global, and a series version of `props`. Which is exactly what I designed it to be. – ThomasReggi Aug 23 '15 at 03:01
  • @ThomasReggi: The danger is that your series has no order. That `delta` function might be executed before your `alpha` function, and you would get `undefined` for `data.alpha`. – Bergi Aug 23 '15 at 12:33

1 Answers1

1

As it's related to the motivation for your question, you should note that in JavaScript non-primitive variables are passed "by reference" to functions. This means that changes to the object within the function are going to be reflected in the object when it is referenced outside the function, and it doesn't matter if it is passed back.

To answer your question, you can just check for equality between the object and the return value.

Notice:

function f(obj) {
    obj.b = 'b test';
    return obj;
}

var obj_1 = {};
obj_1.a = 'a test';
// This is really just a reference to the same object as obj_1
var obj_2 = f(obj_1);

console.log(obj_1); // {a: "a test", b: "b test"}
console.log(obj_2); // {a: "a test", b: "b test"}
// Here is how you can test for equality with the original object passed in
console.log(obj_1 === obj_2); // true
Chris Hunt
  • 3,840
  • 3
  • 30
  • 46
  • This is brilliant, yeah it's the same object variable so I can just check if `start === value` and just return `value` no `clone` necessary. This is amazing, I was way overthinking it. – ThomasReggi Aug 22 '15 at 12:34
  • Happy to help! If this answered your question be sure to mark it. :) – Chris Hunt Aug 22 '15 at 18:41