Question about child.call and returned value
paulcookie opened this issue · comments
Is there some limitations/caveats for child.call returned value as promise, like it's done at child.get functionality? Because if I need to pass some arguments to child model functions, then I need to switch from promise based code to event driven style.. but I wanna use just one of it
@paulcookie sorry it took me so long to respond. 😞
Could you provide a code snippet? ...so I can more clearly see what you want/wanted to do. It would also be cool to see what you came up with!
wow, thanks for reply, even if it took a long time..
Postmate could do things like this:
child.get('childMethod').then(returnedValue => {...})
but if we need to pass some arguments to child method and get returned promise we can't do this easily, we should use event emitter:
// parent
child.on('childMethodSuccess', someData => { ... })
// we should subscribe to event before call to get returned data
child.call('childMethod', args) // and after we could call child with args
// child model
{
childMethod: (args) => {
// some async logic
parent.emit('childMethodSuccess', returnedValue) // instead of return we should emit custom event with our data
}
}
so basically I wanna do things like this:
// parent
child.call('someChildMethod', args).then(someData => { ... }) // call and get Promise as returned value
// child model
{
childMethod: (args) => new Promise((resolve, reject) => {
// some async logic
resolve(returnedValue) // just resolve promise with some data
}
}
in this case we don't need to create and name custom events
@yowainwright is there a reason why the 'call' method does not return a Promise in the same way that the 'get' method does? This could be useful if you want to both (1) pass data across and (2) validate or do something with the response you’re given.
Looking at the code it seems like things could be simplified so 'call' and 'get' use the same API, e.g., if it passed any data
through and then resolveValue
were updated to take a third argument that uses the data iff it’s a function:
/**
* Takes a model, and searches for a value by the property
* @param {Object} model The dictionary to search against
* @param {String} property A path within a dictionary (i.e. 'window.location.href')
* @param {Object} data Additional information from the get request that is
* passed to functions in the child model
* @return {Promise}
*/
export const resolveValue = (model, property, data) => {
const unwrappedContext = typeof model[property] === 'function'
? model[property](data) : model[property]
return Postmate.Promise.resolve(unwrappedContext)
}
(NOTE: strangely data
is already listed as a param of this function in the code!)
EDIT: I’m seeing in the test code an example that does a call, immediately followed by a get, which seems like it could work, but it still seems odd to me that we can’t just get a response from 'call'? https://github.com/dollarshaveclub/postmate/blob/master/test/acceptance/test.js#L50
Just stumbled upon this as well. It seems a little bit strange, that call
wouldn't return a promise like get
. @yowainwright is there any technical limitation that we're not aware of? I think this is a great library, so it would be really nice if it would support this use case.
However, here is my solution to the problem for now (based on @mrcoles observation):
// Save to postmate-async-func.js
/* Creates a function property that takes arguments and returns a promise */
export function makeAsyncFunctionProperty(func) {
let cachedArgs = null;
return (args) => {
if ( args !== undefined ) {
cachedArgs = args;
} else {
return func(cachedArgs).catch(e => new Error(e)); // Does not inclued stack trace
}
}
}
/* Enhances ParentAPI with new method "callAsync" */
export function enhanceChildAsyncCall(child) {
child.callAsync = (fname, data) => {
child.call(fname, data || null);
return Promise.resolve(child.get(fname)).then(res => {
if ( res instanceof Error ) {
throw res;
}
return res;
});
};
return child;
}
The two functions can then be used like this to create async functions that take arguments:
// Your model
import { makeAsyncFunctionProperty } from './postmate-async-func'
const handshake = new Postmate.Model({
asyncMult: makeAsyncFunctionProperty((value) => {
let result = value * 5;
return new Promise((resolve) => {
setTimeout(() => resolve(result), 1000)
})
}),
asyncAdd: makeAsyncFunctionProperty((value) => {
let result = value + 5;
return new Promise((resolve) => {
setTimeout(() => resolve(result), 1000)
})
}),
asyncFail: makeAsyncFunctionProperty((value) => {
return Promise.reject()
}),
}
// Your parent
import { enhanceChildAsyncCall } from './postmate-async-func'
handshake.then(child => {
enhanceChildAsyncCall(child);
child.callAsync('asyncMult', 4).then(res => {
console.log("Got result", res)
});
child.callAsync('asyncAdd', 3).then(res => {
console.log("Got result", res)
});
child.callAsync('asyncFail').catch(e => {
console.log("Got error", e)
});
})
This also takes care of rejected promises.