I have a function that does request to API when the video is finished:
video.addEventListener('ended', example);
var example = function () {
VK.api('video.get', { owner_id: 123 }, function(data) {
/**...*/
}
}
And also I have a replay button (shows when video is finished), which the user can click faster than the response comes from API. And now I need to kill my function. How I can do it?
Link to API: https://vk.com/dev/video.get
There don't seems to be any method to cancel a all from this api.
So either you analyse the code to see how is handled that call (Can't do it for you because we need an auth).
I'd say that your best bet is to set a boolean flag to true while making the call and to false when asking for replay :
//Our flag
var userCanceledCalled = false;
var example = function () {
document.querySelector('span').innerHTML = "new example should be called in few seconds";
//Set it to fals at call
userCanceledCalled = false;
VK.api('video.get', { owner_id: 123 }, function(data) {
if(!userCanceledCalled){
alert(data);
}
});
}
function replay(){
//Set it to true on user action
document.querySelector('span').innerHTML = "new example as been canceled";
userCanceledCalled = true;
}
var b = document.querySelectorAll('button');
b[0].addEventListener('click', example);
b[1].addEventListener('click', replay);
var VK={api: function(data, useless, fn){setTimeout(function(){fn(data)}, 3000)}};
<button>Call a new Example</button></br>
<button>Replay</button>
<span id="log"></span>
There are two approaches:
Cancel the ongoing request
Prevent the callback function from doing work
I have no idea how to do 1. but you can probably ask at the Vkontakte issue tracker https://vk.com/bugs
As for the second one, you can check if the user has already clicked on replay button by the time the response comes back and turn your callback into a noop:
video.addEventListener('ended', function () {
VK.api('video.get', { owner_id: 123 }, function (data) {
// Let's check if user has pressed the replay button and
// if yes then we abort the function:
if (userHasPressedReplay) { return; }
/**...*/
};
});
This is not ideal because the request still hits the server... but it prevents execution of the callback function.
Related
I'm using web BLE. I have based my code according to the example of the heart rate measurement.
Everything is working fine most of the time. But sometimes, even if the connection is successfully made, when I try to bind to the notification, it doesn't work.
The link is made in this function :
_startNotifications(characteristicUuid) {
let characteristic = this._characteristics.get(characteristicUuid);
console.log(characteristic);
return characteristic.startNotifications().then(() => characteristic);
}
When everything is OK, I can see in the console that BluetoothRemoteGATTCharacteristic has a value : DataView(2) {}
Otherwise, when it's not working it has a value : null
I would like to be able to retry automatically, if I detect that the value is null. But I'm not familiar with Promise (I think this is it) and console.log(characteristic.value) doesn't work here.
How would you approach this ?
What I ended up doing is "bypass" the issue. So it's a more algorithmic resolution than a pure Javascript one.
I didn't change the connection function, so it is still called like this :
device._startNotifications(some_uuid).then(handleHeartRateMeasurement)
I check everything in the handleHeartRateMeasurement function :
var ready = false;
function handleHeartRateMeasurement(heartRateMeasurement) {
console.log("Hold on...");
heartRateMeasurement.addEventListener("characteristicvaluechanged", event => {
// Everytime the value change, this should be triggered
// If it did not, variable "ready" will stay false
ready = true;
var value = device.parseValue(event.target.value);
// Do something with value here
});
var check = function(){
// If we have received data from characteristic, we are ready to go !
if(ready === false){
console.log("Device connected but not receiving data");
// Stop the current notification subscription
device.stopNotificationsHeartRateMeasurement();
// Start a new one
device._startNotifications(some_uuid).then(handleHeartRateMeasurement);
setTimeout(check, 1000); // check again in a 1s
}
else{
console.log("Device connected and receiving data");
}
}
setTimeout(() => {
check();
}, 500);
}
Hoping some of you may help me with this problem.
I have a few navigation link on top of my application which have active and not-active state. In theory, I want to jump from one to another and if there exists a form which isn't complete/valid, I trigger validation errors and stay on the same page. What is happening in my case is form validation works fine but navigation links on top change state from non-active to active, whichever was clicked.
I have a ValidateForm function that validates and submits the form if its is valid, else it returns deferred.reject();
function ValidateForm(submitAnyway) {
var deferred = $.Deferred();
var form = $('form');
// if form doesn't exist on the page - quit
if (typeof form[0] === "undefined") return true;
// now check for any validation errors
if (submitAnyway) {
if (!$(form).valid()) {
deferred.reject();
} else {
$(form).submit();
deferred.resolve();
}
} else {
deferred.resolve();
}
return deferred.promise();
}
I have a click event for those top navigation links as below:
var DonutsClickEvent = function (e, arg) {
var url = $(this).attr('data');
var data = { param: arg };
if (typeof url === "undefined") return false;
$.when(window.ValidateForm(false)).then(
function () {
LoadPartialView_Main(url, data);
},
function(){
return false; // I'm trying to return false here to stop event from continuing
}
);
Console.log("This statement runs before $.when completes");
// and event continues and the clicked nav button changes
// its state from non-active to active, even though form doesn't validate
}
I recently added $.Deferred functionality to my code due to some events firing with messed up sequence... Before my validateForm method would return true or false and based on that i'd continue executing event if true, if false i'd stop and it was all good.
Not sure what am I doing wrong here. I'd appreciate any kinda help.
Thanks!
Johny
You can't asynchronously decide whether you want to block the default action or not. By the time you get your async result, your function has already returned and the default action has already occurred.
If you have to validate asynchronously, then you will have to always block the default action. If the validation succeeds, then you will have to manually carry out the desired action with Javascript.
So, assuming that LoadPartialView_Main() is what you want to have happen when validation succeeds, you can do something like this:
var DonutsClickEvent = function (e, arg) {
var url = $(this).attr('data');
var data = { param: arg };
if (typeof url === "undefined") return false;
window.ValidateForm(false).then(function () {
LoadPartialView_Main(url, data);
}, function(err){
// probably show validation error message to the user here
});
// always prevent default action
return false;
}
Also, there's no reason to use $.when() with a single promise. You can just use window.ValidateForm(...).then(...).
I'm using angular-http-auth for intercepting 401 response in order to display login dialogue and when the user is authorized, to retry failed request.
Since I'm using infinity-scroll I'm increasing an offset value, with every additional upload:
var upload = function () {
dataResource.query($scope.model).then(function (result) {
angular.forEach(result.items, function (value) {
$scope.items.push(value);
});
});
}
$scope.uploadMore = function () {
$scope.model.Offset = $scope.model.Offset + 10;
upload();
};
upload();
When my page loads up it immediately sends 2 request to server upload(), invoked from this directive, and uploadMore() by infinity-scroll.
However, after user has logged in, the page does not display the first 10 entries, instead it displays 11-20 items 2 times in a row.
When I tried to debug it, I noticed that when angular-http-auth retries requests it uses increased by 10 Offset value for both queries($scope.module argument).
Functions upload() and uploadMore() are running for 2 times before angular-http-auth, so I guess that is why interceptor uses updated argument for both queries.
Could somebody please help me with this problem?
So you can resolve this problem prevent execute request until previous will finish.
The faster way to do that is :
var pending = false;
var upload = function () {
if(!pending) {
pending = true;
dataResource.query($scope.model).then(function (result) {
pending = false;
angular.forEach(result.items, function (value) {
$scope.items.push(value);
});
});
}
}
I can return a value if I send a sync message:
// frame script
var chromeBtnText = sendSyncMessage("getChromeToolbarButtonText");
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
// chrome script
messageManager.addMessageListener("getChromeToolbarButtonText", listener);
function listener(message) {
return document.getElementById('myChromeToolbarButton').label.value;
}
How do I achieve this with a callback with sendAsyncMessage?
I was hoping to do something like:
// frame script
function myCallback(val) {
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
var chromeBtnText = sendAsyncMessage("getChromeToolbarButtonText", null, myCallback);
There is no callback for replies. In fact, there is no reply at all. The return value from the chrome message listener is simply ignored for async messages.
To do fully async communication, you'd have to send another message containing the reply.
Frame script
addMessageListener("getChromeToolbarButtonTextReply", function(message) {
alert(message.data.btnText);
});
sendAsyncMessage("getChromeToolbarButtonText");
Chrome
messageManager.addMessageListener("getChromeToolbarButtonText", function(message) {
var btnText = document.getElementById('myChromeToolbarButton').label.value;
// Only send message to the frame script/message manager
// that actually asked for it.
message.target.messageManager.sendAsyncMessage(
"getChromeToolbarButtonTextReply",
{btnText: btnText}
);
});
PS: All messages share a namespace. So to avoid conflicts when another piece of code wants to use the same name getChromeToolbarButtonText, you better choose a more unique name in the first place, like prefixing your messages with your add-on name my-unique-addoon:getChromeToolbarButtonText or something like that. ;)
I was also hoping to do something similar:
messageManager.sendAsyncMessage("my-addon-framescript-message", null, myCallback);
I'm going the other direction so the myCallback would be in chrome but it's exactly the same principle.
I'd used similar approaches to #Noitidart and #nmaier before but in this new case I wanted to bind to some local data so myCallback can behave differently based on the application state at the time the first message was sent rather than at the time the callback is executed, all while allowing for the possibility of multiple message round-trips being in progress concurrently.
Chrome:
let someLocalState = { "hello": "world" };
let callbackName = "my-addon-somethingUnique"; // based on current state or maybe generate a UUID
let myCallback = function(message) {
messageManager.removeMessageListener(callbackName, myCallback);
//message.data.foo == "bar"
//someLocalState.hello == "world"
}.bind(this); // .bind(this) is optional but useful if the local state is attached to the current object
messageManager.addMessageListener(callbackName, myCallback);
messageManager.sendAsyncMessage("my-addon-framescript-message", { callbackName: callbackName } );
Framescript:
let messageHandler = function(message) {
let responseData = { foo: "bar" };
sendAsyncMessage(message.data.callbackName, responseData);
};
addMessageListener("my-addon-framescript-message", messageHandler);
There's a real-world example here: https://github.com/luckyrat/KeeFox/commit/c50f99033d2d07068140438816f8cc5e5e290da9
It should be possible for Firefox to be improved to encapsulate this functionality in the built-in messageManager one day but in the mean-time this approach works well and with a surprisingly small amount of boiler-plate code.
in this snippet below. i add the callback before sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarbuttonText'... as i know it will send back. Then I remove it after callback executes. I know I don't have to but just to kind of make it act like real callback, just to kind of show people, maybe it helps someone understand.
Frame:
/////// frame script
function CALLBACK_getChromeToolbarButtonText(val) {
removeMessageListner('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //remove the callback
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
addMessageListener('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //add the callback
var chromeBtnText = sendAsyncMessage("my-addon-id#jetpack:getChromeToolbarButtonText", null);
Chrome:
////// chrome script
messageManager.addMessageListener("my-addon-id#jetpack:getChromeToolbarButtonText", listener);
function listener() {
var val = document.getElementById('myChromeToolbarButton').label.value;
sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage',val);
}
Using the gapi.auth.authorize function, the user can close the popup without clicking any option (no accept or deny button). When this case happens, my callback function doesn't fire, so that I can't handle this case. What's the way to resolve this scenario?
Thanks.
This question has been around for a while, but when I looked into the issue (I want to show a spinner while the google authentication window is open, and hide it if the user decides not to authenticate), and found that gapi is throwing an error popup_closed_by_user. There is a two-second delay (which is kind of long, Facebook's is instant) before it is thrown, but it does work. Hooray, Google!
Some sample code (angular 1.x), prompting is the attribute to show the spinner:
_google_obj.prompting = true;
gapi.auth2.getAuthInstance().signIn().then(function(googleResponse){
var token = googleResponse.getAuthResponse().id_token;
SVC_exec_.post('/q/goog', 1000, { token: token }, 'signing you in through Google', function (response) {
if (response.code === 'ok') {
// update the UI
}
_google_obj.prompting = false;
});
},
function(error){
$timeout(function () {
console.log('user probably closed the google popup window: '+error);
_google_obj.prompting = false;
});
});
They don't appear to mention it in any documentation, but gapi.auth.authorize() returns the popup Window. So you can save the returned Window and set an interval or timeout to check Window.closed.
So you the auth function from Google returns promise, not a window. But then you can wrap original window into the function, which will set interval, to check if opened window closed already.
// variable to store our deferred object
var authDefer = null;
function auth() {
// right before the auth call, wrap window.open
wrap();
// Call auth
authDefer = window.gapi.auth.authorize({
client_id: ...,
scope: ...,
immediate: ...
}).then(
// onSuccess,
// onReject,
// onNotify
);
}
function wrap() {
(function(wrapped) {
window.open = function() {
// re-assign the original window.open after one usage
window.open = wrapped;
var win = wrapped.apply(this, arguments);
var i = setInterval(function() {
if (win.closed) {
clearInterval(i);
if (authDefer) {
authDefer.cancel();
}
}
}, 100);
return win;
};
})(window.open);
}
Taken from one of the thread on Google forums. Really works.
External link to Source