I have a piece of code, which communicate with iOS native layer by dispatching request with callback ID, and storing Deferred in array. Then when the callback comes, we can resolve or reject this Deferred.
My code:
jsGate = (function () {
function jsGate() {}
jsGate._messageCount = 0;
jsGate._callbackDeferreds = {};
jsGate.dispatch = function (plugin, method, args) {
var callbackID, d, message;
callbackID = this._messageCount;
message = {
plugin: plugin,
method: method,
"arguments": args,
callbackID: callbackID
};
send(message)
this._messageCount++;
d = new Deferred;
this._callbackDeferreds[callbackID] = d;
return d.promise;
};
jsGate.callBack = function (callbackID, isSuccess, valueOrReason) {
var d;
d = this._callbackDeferreds[callbackID];
if (isSuccess) {
d.resolve(valueOrReason[0]);
} else {
d.reject(valueOrReason[0]);
}
return delete this._callbackDeferreds[callbackID];
};
return jsGate;
})();
Examples of usage:
jsGate.dispatch("ReadLater", "fetchSomething", []).then(function (value) {
return console.log(value);
});
return jsGate.dispatch("ReadLater", "asyncError", []).then(function (value) {
return console.log(value);
}, function (reason) {
return console.log("Failed: " + reason);
});
What's the best practice for that using A+ Promises?
After long research: there's no clean way of doing this without strange hacks. The main difference between Deferred and Promise is that we can't manipulate the Promise result from outside. In general - it's good approach, but in this specific case, we need that functionality, so - we have to stick with Deferred.
Related
I have a function which returns a promise. I create a jQuery deferred for this purpose, which might be resolved/rejected in custom ways, depending on implementation.
One implementation uses an AJAX call, and there I'd like to redirect or queue the failure/resolution of the AJAX promise to the one which was created earlier. This means that whenever the AJAX call has a resolve/reject/progress, the deferred should trigger its own resolve/reject/progress too with the same arguments.
Here is some dummy sample code.
function Test() {
}
Test.prototype.doSomething() {
this._deferred = $.Deferred();
this.doSomethingImpl();
return this._deferred;
}
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
// resolve/reject/progress here the this._deferred based on the ajax promise
}
I know I can do it in a verbose way using the AJAX done, fail and progress callbacks, and manually call my deferred's corresponding method (resolve, reject or progress), but I'm seeking for kind of a one-liner, if there is any.
EDIT
Here is a code which is similar to the real one, using knockoutjs.
function GridViewModel() {
var self = this;
self.pageIndex = ko.observable(0);
...
self._refreshRequest = ko.observable(null).extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } });
self._doRefresh = function() {
$.ajax(...)
.done(result) { // update rows, etc. }
.then(
function(r) { self._refreshPromise.resolve(r); },
function(r) { self._refreshPromise.reject(r); },
function(r) { self._refreshPromise.progress(r); }
)
.always(function() { self._refreshPromise = null; }
// here I used the obvious verbose redirecting
}
...
ko.computed(function() {
var pageIndex = self.pageIndex();
if (ko.computedContext.isInitial()) return;
this.refreshRequest("Paging");
});
ko.computed(function() {
var refreshRequest = self.refreshRequest();
if (ko.computedContext.isInitial() || !refreshRequest) return;
self._doRefresh(refreshRequest);
}
}
GridViewModel.prototype.Refresh = function(type) {
this._refreshPromise = this._refreshPromise || $.Deferred();
this._refreshRequest(type);
return this._refreshPromise;
}
This code is a snippet of a complex data grid viewmodel class, and the fancy refresh solution is there to ensure that refreshing is throttled.
Yes, it would be possible to redirect the resolution (in a perfect world1, just deferred.resolve(promise)), but it's completely unnecessary. Don't create deferreds when you're already calling something that produces a promise for you - avoid the deferred antipattern! You can simply return that very promise:
Test.prototype.doSomething = function() {
return this.doSomethingImpl();
};
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
return ajax; // the promise
};
1) where jQuery follows the Promises/A+ specification and deferred.resolve accepts thenables
I use the promise join and I need to use send the data from the readFile to myFacade (src) and my facade send obj to getA which latter on will be sent to arg[0]...
run = function (filePath) {
return Promise.join(
fs.readFileAsync(filePath, 'utf8')
.then(myFacade)
.then(getA),
users.getUsersAsync(usersObj)
.then(users.modifyRec.bind(null, process.env.us))
).then(function (args) {
return runProc('run', args[0], args[1]);
....
To make this work not in promise you should do something like
var parsed = new MyFacade(str);
var attribute = parsed.getA()
This is the code which should be called
var yaml = require('yamljs');
function MyFacade(src) {
this.data = yaml.parse(src);
}
MyFacade.prototype = {
getA: function () {
return this.data.def_types.web;
},
getB: function () {
return this.data.conars;
}
};
module.exports = MyFacade;
how to make it work with the promise chain above?
Just pass exactly the code you'd have used without promises as a callback:
return Promise.join(
fs.readFileAsync(filePath, 'utf8')
.then(function(str) {
var parsed = new MyFacade(str);
var attribute = parsed.getA()
return attribute;
}),
users.getUsersAsync(usersObj)
.then(users.modifyRec.bind(null, process.env.us)),
function(attr, rec) {
return runProc('run', attr, rec);
});
You're using
.then(getA)
Which means "call the function getA on the result of the previous promise." But you don't have a function getA; the result of the previous promise has a method getA. You want call:
.call('getA')
As for
.then(myFacade)
There are two options. One is a common thing to add to a constructor function:
function MyFacade(src) {
if(!(this instanceof MyFacade)) return new MyFacade(src);
this.data = yaml.parse(src);
}
This allows the constructor to be called without new. Alternatively, you can pass an anonymous function to then:
.then(function(str) {
return new MyFacade(str);
})
I'm using D.js as promise library for our javascript application.
Following is my sample code:
function getData(deferred) {
var data_one;
// getInfo is returning a promise for async task
getInfo()
.then(function (resp_one) {
data_one = resp_one;
// getInfo2 is also returning another promise
return getInfo2();
})
.then(function (resp_two) {
deferred.resolve('prefix' + data_one + resp_two);
});
};
function sample () {
var d = D(),
data = localStorage.getItem('key');
if (data) {
d.resolve(data);
} else {
getData(d);
}
return d.promise;
}
sample().then(function (data) {
//do something with data.
});
I im invoking sample function. Is the implementation inside the sample function and sub functions following the coding standard for promises?
Im new to promises, Is it good to passing around deferred object to other function to resolve/reject ??
Is there any better way to implement the above functionality??
Thanks in advance..
Looks like you can improve the code if you use promises in more natural way.
First of all if getData returns a Promise then you don't have to pass deferred around, this is considered anti-pattern. You just simply return getInfo().
Another thing, in the sample function if your data might be already available it's convenient to use D.promisify method to return resolved promise with non-promise value:
function getData() {
var data_one;
return getInfo().then(function (resp_one) {
data_one = resp_one;
return getInfo2();
})
.then(function (resp_two) {
return 'prefix' + data_one + resp_two;
});
};
function sample() {
var data = localStorage.getItem('key');
return data ? D.promisify(data) : getData();
}
sample().then(function (data) {
//do something with data.
});
I have a function that uses two ajax calls in order to get the proper information:
var getUsers = function() {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(foo) {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(bar) {
return foo['age'] = bar.type;
});
});
}
And an outside function that calls the current function and only continues when the calls are finished.
getUsers().then(function(result) {
// ...
});
Now the weird thing is that if I display the result, the 'age' will show up in the console, but if I try to access it using result['age'], it will return undefined.
Is there a proper way of handling multiple deferred calls?
Code
http://codepen.io/norbiu/pen/bNRQxL
Edit Instead of using a separate deferred, you can chain the ones returned from getJSON() like this
var getUsers = function() {
var foo;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.then(function(data) {
foo = data;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
}).then(function(bar) {
foo['age'] = bar.type;
return foo;
});
}
Note: you need to save the return value from the first call or it won't be accessible to the second.
Original code for posterity
You can use a jQuery Deferred object and return that instead
var getUsers = function() {
var dfd = $.Deferred();
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(foo) {
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(bar) {
foo['age'] = bar.type;
dfd.resolve(foo);
}).fail(function(e) {
dfd.reject(e);
})
}).fail(function(e) {
dfd.reject(e);
});
return dfd.promise();
}
http://codepen.io/anon/pen/pvwqZo
The deferred object won't resolve until both requests succeed (and will fail if any of them fail).
I am currently learning alot more about callback functions and want to create my own callback functions with a success and failure.
I have written for a Person object a game of rock paper sissors
Person = (function() {
function Person(name) {
this.name = name;
}
Person.prototype.randomRps = function() {
var choices, randomChoice;
choices = ["rock", "paper", "sissor"];
return randomChoice = choices[Math.floor(Math.random() * choices.length)];
};
Person.rockPaperSissor = function(player1, player2) {
return player1.randomRps() === player2.randomRps();
};
return Person;
})();
I want to call Person.rockPaperSissor(p1,p2).then(...) but don't know how I should write this .then() function to chain it to the .rockPaperSissor()
Similar to how jQuery has $.get() and a .success() and error() functions to chain.
Thanks!
You just need to return this at your function
Person.rockPaperSissor = function(player1, player2) {
this.state = player1.randomRps() === player2.randomRps();
return this;
};
Person.then = function() {
var x = this.state;
};
Assuming that something in Person.rockPaperSissor is async, you need to return some sort of Promise object. As others have noted, if the result here is synchronous, then there's no point in this particular misdirection. JQuery provides an implementation in DeferredObject; if you want to write your own, you'd need to work out what methods this object should support.
To take a relatively simple case, you could make a Promise class that only supported .then like this:
function Promise() {
// start unresolved
this.resolved = false;
// init list of callbacks to fire on resolution
this.callbacks = [];
}
Promise.prototype = {
then: function(callback) {
if (this.resolved) {
// if resolved, fire immediately
callback();
} else {
// otherwise, queue up the callback for later
this.callbacks.push(callback);
}
},
resolve: function() {
this.resolved = true;
// fire all callbacks
this.callbacks.forEach(function(callback) {
callback();
});
}
};
Then, in your async function, you'd do something like:
Person.rockPaperSissor = function(player1, player2) {
var promise = new Promise();
doSomethingAsync(function callback() {
promise.resolve();
});
return promise;
};
In your specific case, .then won't be super-useful unless you expose the results of the rockPaperSissor function as persistent state on Person; if you want to pass the result to the callbacks, you'll need slightly more involved handling to pass arguments to your callbacks and potentially handle failure cases.