I consider rewriting existing callback-based code into code using promises. However I'm unsure if this makes sense and how to start. The following code-snippet is a mostly self-contained example from that code:
function addTooltip($element, serverEndpoint, data) {
'use strict';
const DELAY = 300;
const TOOLTIP_PARENT_CLASS = 'hasTooltip';
let timeOutReference;
$element.hover(function hoverStart() {
if ($element.hasClass(TOOLTIP_PARENT_CLASS)) {
return;
}
timeOutReference = setTimeout(function getToolTip() {
const $span = jQuery('<span class="serverToolTip">');
$span.html(jQuery('<span class="waiting">'));
$element.append($span);
$element.addClass(TOOLTIP_PARENT_CLASS);
jQuery.get(serverEndpoint, data).done(function injectTooltip(response) {
$span.html(response.data);
}).fail(handleFailedAjax);
}, DELAY);
}, function hoverEnd() {
clearTimeout(timeOutReference);
});
};
Intended functionality: When the user hovers over $element for 300ms the tooltip-content is requested from the server and appended to $element.
Does it make sense to rewrite that code with promises and how would I do it?
(jQuery is provided by the framework (dokuwiki), so we might as well use it.)
Prior research:
https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
other SO questions about this topic which left me unsure about whether this is a sensible idea and how to do it
First, you'd need to wrap setTimeout into a promise. Simply create a function that accepts a timeout and returns a promise that resolves after that timeout.
Next, since jQuery.get already returns a promise, you just need to put it inside the promise resolve handler and return its promise. That way the next chained then listens to that promise instead of the timer's.
It would look something like:
function timer(n){
return Promise(function(resolve){
setTimeout(resolve, n);
});
}
timer(DELAY).then(function(){
return jQuery.get(...)
}).then(function(response){
// jQuery.get promise resolved
}).catch(function(error){
// something failed somewhere
});
As for your question
Does it make sense to rewrite that code with promises and how would I do it?
That depends on you. I find promise-based code more readable but takes time to write properly especially if if you intend to write pure callbacks and deal with multiple async operations. I usually write my code callbacks-first if the API is simpler to write that way and refactor later for readability.
To elaborate on my comment above. Below is an example of how promises can make dependent-callback code (arguably) more readable (Basically, it destroys the nesting of callbacks-in-callbacks):
Again, in the case of the code snippet you posted, I hardly see how it's worth it (unless your doing it as an exercise).
With Callbacks
function someAsyncMethod(callback) {
$.get({...})
.then(callback);
}
function anotherAsyncMethod(callback) {
$.get({...})
.then(callback);
}
someAsyncMethod(function() {
anotherAsyncMethod(function yourFunction() {
//do something
});
});
With Promises:
function someAsyncMethod() {
return $.get({...});
}
function anotherAsycnMethod() {
return $.get({...});
}
someAsyncMethod()
.then(anotherAsyncMethod)
.then(function yourFunction() {
//do something
})
Related
There could be a simple answer for this but I've only ever had to use extension methods (are they even called that in JS?) in C#..
I have a 3rd party library that uses events. I need a function to be called after the event is called. I could do this easily via a promise but the 3rd party library was written before ES6 and does not have promises built in. Here's some sample code -
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!')
})
Ideally I would like to be able to implement something like this -
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!')
}).then(wRcon.reconnect())
So to summarize my question, how do I extend wRcon.on to allow for a promise (or some other callback method)?
Promises and events, while they seem similar on the surface, actually solve different problems in Javascript.
Promises provide a way to manage asynchronous actions where an outcome is expected, but you just don't know how long it's going to take.
Events provide a way to manage asynchronous actions where something might happen, but you don't know when (or even if) it will happen.
In Javascript there is no easy way to "interface" between the two.
But for the example you've given, there is an easy solution:
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!')
wRcon.reconnect()
})
This is perhaps not as elegant as your solution, and breaks down if your intention is to append a longer chain of .then() handlers on the end, but it will get the job done.
You could wrap the connection in a way that would create a promise:
const promise = new Promise((resolve) => {
wRcon.on('disconnect', function() {
resolve();
})
}).then(wRcon.reconnect())
However, this does not seem like an ideal place to use Promises. Promises define a single flow of data, not a recurring event-driven way to deal with application state. This setup would only reconnect once after a disconnect.
Three/four options for you, with the third/fourth being my personal preference:
Replacing on
You could replace the on on the wRcon object:
const original_on = wRcon.on;
wRcon.on = function(...args) {
original_on.apply(this, args);
return Promise.resolve();
};
...which you could use almost as you showed (note the _ =>):
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!');
}).then(_ => wRcon.reconnect());
...but I'd be quite leery of doing that, not least because other parts of wRcon may well call on and expect it to have a different return value (such as this, as on is frequently a chainable op).
Adding a new method (onAndAfter)
Or you could add a method to it:
const original_on = wRcon.on;
wRcon.onAndAfter = function(...args) {
wRcon.on.apply(this, args);
return Promise.resolve();
};
...then
wRcon.onAndAfter('disconnect', function() {
console.log('You have been Disconnected!');
}).then(_ => wRcon.reconnect());
...but I don't like to modify other API's objects like that.
Utility method (standalone, or on Function.prototype)
Instead, I think I'd give myself a utility function (which is not "thenable"):
const after = (f, callback) => {
return function(...args) {
const result = f.apply(this, args);
Promise.resolve().then(callback).catch(_ => undefined);
return result;
};
};
...then use it like this:
wRcon.on('disconnect', after(function() {
console.log('You have been Disconnected!');
}, _ => wRcon.reconnect()));
That creates a new function to pass to on which, when called, (ab)uses a Promise to schedule the callback (as a microtask for the end of the current macrotask; use setTimeout if you just want it to be a normal [macro]task instead).
You could make after something you add to Function.prototype, a bit like bind:
Object.defineProperty(Function.prototype, "after", {
value: function(callback) {
const f = this;
return function(...args) {
const result = f.apply(this, args);
Promise.resolve().then(callback).catch(_ => undefined);
return result;
};
}
});
...and then:
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!');
}.after(_ => wRcon.reconnect()));
And yes, I'm aware of the irony of saying (on the one hand) "I don't like to modify other API's objects like that" and then showing an option modifying the root API of Function. ;-)
So I have this problem. I'm fairly new to angular and I've been told to modify a directive which manages forms to make the submit button disabled then enabled again when all the work is done.
Since the functions being called usually have async calls, simply adding code sequentially doesn't work.
var ngSubmit = function() {
vm.disabled = true;
$scope.ngSubmitFunction();
vm.disabled = false;
}
Button is enabled before async calls under ngSubmitFunction() finish.
So I thought a promise would fix that and made something like:
var promise = function() {
return $q(function (resolve) {$scope.ngSubmitFunction()});
}
var ngSubmit = function() {
vm.disabled = true;
promise().then(function() {
vm.disabled = false;
});
}
This doesn't output any error but never enables the button again (.then is never called).
Tried different kind of promises declaration, all with the same result, except for this one:
$scope.submitPromise = function() {
return $q.when($scope.ngSubmitFunction());
}
This does call .then function, but again, doesn't wait for any child async function to finish. '.then' is called instantly, like the sequential version.
Have in mind that I don't know what's under ngSubmitFunction(). It is used by dozens developers and it may contain from 0 to multiple async calls. But typical scenario is something like:
ngSubmitFunction() calls func()
-- func() decides wether to call create() or update()
-- -- update() calls a elementFactory.update() which is an async call
-- -- -- elementFactory.update().then(function()) is called when finished.
-- -- -- -- At THIS point, I should enable the button again.
How can I achieve this? is there a way to chain promises with non-promises functions in between? or another way to make a code only execute when everything else is done? I thought about creating an event at DataFactory when an async call is over but if the update() function was calling to more than one async call this wouldn't work.
If you are using promises, your async functions should return promises, if they do it should work like this:
var ngSubmit = function() {
vm.disabled = true;
$scope.ngSubmitFunction().then(() => {
vm.disabled = false;
});
}
I don't know what's under ngSubmitFunction()
Then you're out of luck, promises won't help you here. Promises or $q.when cannot magically inspect the call and see what asynchronous things it started or even wait for them - ngSubmitFunction() needs to return a promise for its asynchronous results itself.
You need to make every function in your codebase which (possibly) does something asynchronous that needs to be awaitable return a promise. There's no way around this.
Well, in case someone wonders, we haven't found a solution to that (there probably isn't). So we will go with the adding returns to all the chain of functions to make sure the ngSubmitFunction recieves a promise and therefor can wait for it to finish before calling '.then'. Not only this makes it work for the cases where there's only one promise implied but it is also a good programming practice.
Cases with more than one promise are scarce so we will treat them individually on the controller itself.
Thank you all for your comments.
Let's say you have:
function setTimeoutPromise(ms) {
var defer = Q.defer();
setTimeout(defer.resolve, ms);
return defer.promise;
}
and then you have something like:
function foo(ms) {
return setTimeoutPromise(ms).then(function () {
console.log('foo');
});
}
function afterItAll() {
console.log('after it all');
}
foo(100).then(function () {
console.log('after foo');
}).then(afterItAll);
Is there a way to modify foo so that afterItAll is executed after the after foo block? e.g. something like:
function foo(ms) {
return setTimeoutPromise(ms).then(function () {
console.log('foo');
}).SOMEPROMISECONSTRUCT(function () {
console.log('after it all');
});
}
foo(100).then(function () {
console.log('after foo');
});
The reason I ask is that I am developing an API where the user will make several of these foo calls and it would greatly cut down on the user's code if the after foo code was automatically executed after these API calls. I know I can use a callback to accomplish this, but I'd really like to stick with just using promises.
No, there is none.
Well, let's see what you're asking here:
Is there a way to modify foo so that afterItAll is executed after the after foo block?
This is effectively asking:
Is there any way to know when no more .then handlers will be added to a specific promise?
Which, given an arbitrary function we can decide to add a fooResult.then(function(){}) as the very last thing in the program before we return from it, so it's like asking:
Is there any way to know when/if a function will return?
Which, given a whole program as a function, is like asking:
Is there any way to know if a program will ever halt?
It's not an easy thing to do to say the least. Not only is this feature non existent, it is theoretically impossible.
So how do I deal with it?
Bergi's answer gives you a pretty good idea. The core here, that we fought for in Bluebird is nesting.
Because we want something that's impossible in the general case we have to invert control, like callbacks do:
function transaction(fn){
// Promise.resolve().then( in ES6/Bluebird promises
return Q().then(function(){
return fn()
}).finally(function(){ // same in Bluebird, in ES6 that's `.then(fn, fn)`
console.log("after it all!");
})
}
This would let you do:
transaction(function(){
return setTimeoutPromise().then(more).then(more);
});
Which would run the setTimeoutPromise and then the more and then the other more and will log "after it all" after both are done. This pattern is very useful for DB drivers and resource acquisition.
No, there is no such promise construct. A promise does not - can not - know whether it is the end of a chain, or whether some other code will attach another link to it.
There is however nothing wrong with combining promises and callback code:
function foo(ms, within) {
return setTimeoutPromise(ms).then(function () {
console.log('foo');
})
.then(within)
.then(function afterFooAll() { // possibly use `finally` here?
console.log('cleanup');
});
}
foo(100, function () {
console.log('inside foo');
}) // now returns a promise for cleanup been done
I'm not sure what your actual use case is here, but you also might want to have a look at Bluebird's Promise.using resource management pattern.
OK! I hope my experience helps you.
I had very similar problem.
I wanted mysql connection pool to be released after all sql statement executed,
or failed... like below
getConnection
.then(exeuteSQL('select ...'))
.then(exeuteSQL('select ...'))
.then(null, function(err){ //err handling })
....
.. finally execute pool release
This can be done .done() method like this
getConnection
.then(exeuteSQL('select ...'))
.then(exeuteSQL('select ...'))
.then(null, function(err){ //err handling })
....
.done(function(){
console.log('this line always executed!');
req.conn.release(); // assuming connection attached to request object before
})
PS) My application Environment is node.js and using 'q' promise module
I have a situation where my WinJS app wants to call a function which may or may not be async (e.g. in one situation I need to load some data from a file (async) but at other times I can load from a cache syncronously).
Having a look through the docs I though I could wrap the conditional logic in a promise like:
A)
return new WinJS.Promise(function() { // mystuff });
or possibly use 'as' like this:
B)
return WinJS.Promise.as(function() { // mystuff });
The problem is that when I call this function, which I'm doing from the ready() function of my first page like this:
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function () {
Data.Survey.init().done(function (result) {
// do some stuff with 'result'
});
}
});
When it is written like 'A' it never hits my done() call.
Or if I call it when it's written like 'B', it executes the code inside my done() instantly, before the promise is resolved. It also looks from the value of result, that it has just been set to the content of my init() function, rather than being wrapped up in a promise.
It feels like I'm doing something quite basically wrong here, but I'm unsure where to start looking.
If it's any help, this is a slimmed down version of my init() function:
function init() {
return new WinJS.Promise(function() {
if (app.context.isFirstRun) {
app.surveyController.initialiseSurveysAsync().then(function (result) {
return new WinJS.Binding.List(result.surveys);
});
} else {
var data = app.surveyController.getSurveys();
return new WinJS.Binding.List(data);
}
});
}
Does anyone have any thoughts on this one? I don't believe the 'may or may not be async' is the issue here, I believe the promise setup isn't doing what I'd expect. Can anyone see anything obviously wrong here? Any feedback greatly appreciated.
Generally speaking, if you're doing file I/O in your full init routine, those APIs return promises themselves, in which case you want to return one of those promises or a promise from one of the .then methods.
WinJS.Promise.as, on the other hand, is meant to wrap a value in a promise. But let me explain more fully.
First, read the documentation for the WinJS.Promise constructor carefully. Like many others, you're mistakenly assuming that you just wrap a piece of code in the promise and voila! it is async. This is not the case. The function that you pass to the constructor is an initializer that receives three arguments: a completeDispatcher function, an errorDispatcher function, and a progressDispatcher function, as I like to call them.
For the promise to ever complete with success, complete with an error, or report progress, it is necessary for the rest of the code in the initializer to eventually call one of the dispatchers. These dispatchers, inside the promise, then loop through and call any complete/error/progress methods that have been given to that promise's then or done methods. Therefore, if you don't call a dispatcher at all, there is no completion, and this is exactly the behavior you're seeing.
Using WinJS.Promise.as is similar in that it wraps a value inside a promise. In your case, if you pass a function to WinJS.promise.as, what you'll get is a promise that's fulfilled with that function value as a result. You do not get async execution of the function.
To achieve async behavior you must either use setTimeout/setInterval (or the WinJS scheduler in Windows 8.1) to do async work on the UI thread, or use a web worker for a background thread and tie its completion (via a postMessage) into a promise.
Here's a complete example of creating a promise using the constructor, handling complete, error, and progress cases (as well as cancellation):
function calculateIntegerSum(max, step) {
if (max < 1 || step < 1) {
var err = new WinJS.ErrorFromName("calculateIntegerSum", "max and step must be 1 or greater");
return WinJS.Promise.wrapError(err);
}
var _cancel = false;
//The WinJS.Promise constructor's argument is a function that receives
//dispatchers for completed, error, and progress cases.
return new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
var sum = 0;
function iterate(args) {
for (var i = args.start; i < args.end; i++) {
sum += i;
};
//If for some reason there was an error, create the error with WinJS.ErrorFromName
//and pass to errorDispatch
if (false /* replace with any necessary error check -- we don’t have any here */) {
errorDispatch(new WinJS.ErrorFromName("calculateIntegerSum", "error occurred"));
}
if (i >= max) {
//Complete--dispatch results to completed handlers
completeDispatch(sum);
} else {
//Dispatch intermediate results to progress handlers
progressDispatch(sum);
//Interrupt the operation if canceled
if (!_cancel) {
setImmediate(iterate, { start: args.end, end: Math.min(args.end + step, max) });
}
}
}
setImmediate(iterate, { start: 0, end: Math.min(step, max) });
},
//Cancellation function
function () {
_cancel = true;
});
}
This comes from Appendix A ("Demystifying Promises") of my free ebook, Programming Windows Store Apps in HTML, CSS, and JavaScript, Second Edition (in preview), see http://aka.ms/BrockschmidtBook2.
You would, in your case, put your data initialization code in the place of the iterate function, and perhaps call it from within a setImmediate. I encourage you to also look at the WinJS scheduler API that would let you set the priority for the work on the UI thread.
In short, it's essential to understand that new WinJS.Promise and WinJS.Promise.as do not in themselves create async behavior, as promises themselves are just a calling convention around "results to be delivered later" that has nothing inherently to do with async.
I'm interacting with a third-party JavaScript library where some function calls are asynchronous. Instead of working the asynchronous logic into my application, I preferred to write synchronous wrappers to those async calls. I know, I know, it's terrible design, but this is a demo project with very high chance of being rewritten entirely. I need something to show the team the concept, not really having to worry performance, yet.
Here's what I wanna do:
function sync_call(input) {
var value;
// Assume the async call always succeed
async_call(input, function(result) {value = result;} );
return value;
}
I tried the jQuery's deferred and promise but it seems to be aiming at the async design pattern. I want to use the synchronous pattern in my code.
This will never work, because the JS VM has moved on from that async_call and returned the value, which you haven't set yet.
Don't try to fight what is natural and built-in the language behaviour. You should use a callback technique or a promise.
function f(input, callback) {
var value;
// Assume the async call always succeed
async_call(input, function(result) { callback(result) };
}
The other option is to use a promise, have a look at Q. This way you return a promise, and then you attach a then listener to it, which is basically the same as a callback. When the promise resolves, the then will trigger.
How about calling a function from within your callback instead of returning a value in sync_call()?
function sync_call(input) {
var value;
// Assume the async call always succeed
async_call(input, function(result) {
value = result;
use_value(value);
} );
}
Here is a working example of how:-
function testAsync(){
return new Promise((resolve,reject)=>{
//here our function should be implemented
setTimeout(()=>{
console.log("Hello from inside the testAsync function");
resolve();
;} , 5000
);
});
}
async function callerFun(){
console.log("Caller");
await testAsync();
console.log("After waiting");
}
callerFun();
Outputs:
Caller
Hello from inside the testAsync function
After waiting
To make it more complete, error handling should be added (deal with the reject() case).
See here for other examples: https://www.delftstack.com/howto/javascript/javascript-wait-for-function-to-finish/