I'm trying to run some simple JS functions after every request to the server with the Fetch API. I've searched for an answer to this question, but haven't found any, perhaps due to the fact that the Fetch API is relative recent.
I've been doing this with XMLHttpRequest like so:
(function () {
var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
this.addEventListener('load', function () {
someFunctionToDoSomething();
});
origOpen.apply(this, arguments);
};
})();
Would be great to know if there's a way to accomplish this very same global thing using the Fetch API.
Since fetch returns a promise, you can insert yourself in the promise chain by overriding fetch:
(function () {
var originalFetch = fetch;
fetch = function() {
return originalFetch.apply(this, arguments).then(function(data) {
someFunctionToDoSomething();
return data;
});
};
})();
Example on jsFiddle (since Stack Snippets don't have the handy ajax feature)
Just like you could overwrite the open method you can also overwrite the global fetch method with an intercepting one:
fetch = (function (origFetch) {
return function myFetch(req) {
var result = origFetch.apply(this, arguments);
result.then(someFunctionToDoSomething);
return result; // or return the result of the `then` call
};
})(fetch);
Related
in my chrome extension I need to use chrome storage. In my background script first I create an object and add it to chrome storage and then I want to get my object from there and to be returned. Something like that:
...
var obj = {};
chrome.storage.local.set(obj, function () { });
...
var data = getData(obj); // I want my object to be returned here
var returnedData = null;
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
returnedData = result; // here it works, I can do something with my object
});
return returnedData; // here it doesn't work
}
As far as I understood from here chrome.storage.local.get is asynchronous with its consequences. But is there any way how to get something from chrome storage and make it to be returned? I mean maybe I should wrap chrome.storage.local.get in another function or so?
Many thanks in advance!
If you want to stay away from global variables and you're okay with modern browser requirements, then you can implement a native JavaScript Promise object. For example, here's a function that returns the stored data for a single given key:
function getData(sKey) {
return new Promise(function(resolve, reject) {
chrome.storage.local.get(sKey, function(items) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
reject(chrome.runtime.lastError.message);
} else {
resolve(items[sKey]);
}
});
});
}
// Sample usage given this data:
// { foo: 'bar' }
getData('foo').then(function(item) {
// Returns "bar"
console.log(item);
});
If you need support for IE11 and below, then you'll have to turn to a library like jQuery.
No it's not possible
But there are several ways around this problem
Do everything you want to do with the data returned from .get() inside the callback (or start it from there using function calls). This is what #wernersbacher posted
Take a look at deferreds (jQuery or Q libraries). A deferred's promise can be returned from getData. Inside the .get() callback, you can resolve the deferred. Outside of getData you can use .then() to do something after the deferred resolved
Something like this
function getData(obj) {
var deferred = $.Deferred();
chrome.storage.local.get(obj, function(result) {
deferred.resolve(result);
});
return deferred.promise();
}
$.when(getData(obj)).then(function(data) {
// data has value of result now
};
You have to do it like that:
var returnedData = null;
function setData(value) {
returnedData = value;
}
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
setData(result); // here it works, I can do something with my object
});
return; // here it doesn't work
}
..because you tried to return a value which did not get read from storage yet, so it's null.
Update with Manifest V3 :
Now chrome.storage.local.get() function returns a promise that you can chain or can await in an async function.
const storageCache = { count: 0 };
// Asynchronously retrieve data from storage.local, then cache it.
const initStorageCache = chrome.storage.local.get().then((items) => {
// Copy the data retrieved from storage into storageCache.
Object.assign(storageCache, items);
});
Note : You must omit the callback paramter to get the promise.
Reference : https://developer.chrome.com/docs/extensions/reference/storage/#:~:text=to%20callback.-,get,-function
You need to handle it with callback functions. Here are two examples. You use a single function to set, however you create a separate function for each "On Complete". You could easily modify your callback to pass additional params all the way through to perform your needed task.
function setLocalStorage(key, val) {
var obj = {};
obj[key] = val;
chrome.storage.local.set(obj, function() {
console.log('Set: '+key+'='+obj[key]);
});
}
function getLocalStorage(key, callback) {
chrome.storage.local.get(key, function(items) {
callback(key, items[key]);
});
}
setLocalStorage('myFirstKeyName', 'My Keys Value Is FIRST!');
setLocalStorage('mySecondKeyName', 'My Keys Value Is SECOND!');
getLocalStorage('myFirstKeyName', CallbackA);
getLocalStorage('mySecondKeyName', CallbackB);
// Here are a couple example callback
// functions that get executed on the
// key/val being retrieved.
function CallbackA(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}
function CallbackB(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}
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).
So we all know that 'this' is a tricky keyword in JavaScript, and anonymous functions and AngularJS promises make it even trickier.
QUESTION (TL&DR Version)
What is the right (and angular) way to allow promise callbacks
to use the same "this" as the service that initiated the request?
See this fiddle for an example:
http://jsfiddle.net/tpeiffer/CFD3e/
All of these methods on the controller calls off to the Tier1Service. Tier1Service then calls off to the WorkerService to get data. When the data is loaded, it returns said data via a promise to the Tier1Service. The data returned gets set into the Tier1Service_data property.
Alternate 3 is clean and it works, but it feels like there has to be a better way.
Alternate 4 is also very clean and it works, but again it seems wrong.
Now what I would REALLY like is for $q's promise to do all of this for me. :)
Here is the relevant code:
// App.js
angular.constructor.prototype.call = function (scope, func) {
return function () {
func.apply(scope, arguments);
};
};
// Tier1Service
get coolData() {
return this._data;
},
set coolData(val) {
this._data = val;
},
doWorkAlt1: function () {
mySubWorkerService.someData.then(function (data) {
// FAILS because 'this' is the window,
// not the service
if (data) this._data = data;
});
},
doWorkAlt2: function () {
mySubWorkerService.someData.then((function (data) {
// FAILS because data is undefined because
// the function is wrapped in an anonymous
// function
if (data) this._data = data;
}).call(this));
},
doWorkAlt3: function () {
// WORKS because I keep track of the instance
var instance = this;
mySubWorkerService.someData.then(function (data) {
if (data) instance._data = data;
});
},
doWorkAlt4: function () {
// WORKS because I keep pass 'this' around
mySubWorkerService.someData.then(angular.call(this, function (data) {
if (data) this._data = data;
}));
}
// WorkerService
get someData() {
var deferred = $q.defer();
deferred.resolve('i got back data!!');
return deferred.promise;
}
You should be able to use Function.bind to achieve the result you want:
doWork: function () {
mySubWorkerService.someData.then((function(data) {
//this now refers to whatever it referred to in the doWork function
}).bind(this));
}
Do note however that bind is not available in older browsers. However it's very easy to patch it in to the prototype manually if necessary.
In the following code, I am retrieving data using $.getJSON (returned form the repository) and $.when as the last call is dependent on data from the first:
var getData =
function () {
var data = { userData: null, userTitles: null, userPage: null };
$.when(repository.getUserDetails().done(f1)),
repository.getUserPolicyTitles().done(f2)
.then(repository.getUserPage().done(f3));
function f1(userData) { data.userData = userData; console.log(data.userData) };
function f2(userTitles) { data.userTitles = userTitles; console.log(data.userTitles) };
function f3(userPage) { data.userPage = userPage; console.log(data.userPage) };
return data;
}
return {
getData: getData
};
Most of this works fine. However, I would like to return the data back to a calling module but it returns before the data is ready as I suppose you would expect.
What is the best way to achieve this?
Thanks
Davy
Your usage of deferred seems incorrect. This is my interpretation.
Also, you need to consider that once you start invoking asynchronous methods, you can't make a synchronous return call, which is what you're doing. Instead of returning data, you need to return a promise; then you provide the promise with a callback that will do stuff with the data
var getData = function () {
var myDeferred = $.Deferred();
var data = { userData: null, userTitles: null, userPage: null };
$.when(repository.getUserDetails().done(f1),
repository.getUserPolicyTitles().done(f2),
repository.getUserPage().done(f3)).then(
function(){ myDeferred.resolve(data); },
function(){ myDeferred.reject.apply(myDeferred, arguments); });
//... f1, f2, f3
return myDeferred.promise();
}
return {
getData: getData
};
Then, when you want to actually use the data, you do
your_library.getData().then(function(data){
// magic goes here
}, function(errors_of_some_sort) {
// error handling magic goes here
});
When using $.when, you can only get the data after all your AJAX calls have returned. So for example:
$.when($.getJSON('somewhere'), $.getJSON('somewhere2'))
.done(function(r1, r2){
//set your data object with the responses from the server here
});
So the short answer is you can't "return" the data you retrieved from the server, but you can assign a callback to use the data (or set to some entity of your module) when the data is ready.
I'm probably missing something simple but given this JS code:
var WS = {
whoami: function () {
var toReturn;
$.getJSON("/SecurityData/GetCurrentUser", function (data) {
toReturn = data.Email;
});
return toReturn;
}
}
When I call it, if I put a breakpoint on the toReturn = data.Email, the expected data is there but if don't WS.whoami is undefined.
I assume this is because the $.getJSON call is async, but how can I get the desired effect?
Ajax is asynchronous and returns a promise object. Instead, return the promise object and add a callback to it.
var WS = {
whoami: function () {
return $.getJSON("/SecurityData/GetCurrentUser");
}
};
WS.whoami().done(function(data){
alert(data.Email);
});
The only other option would be to make it a synchronous request, however I do not recommend it due to the impact it will have on your UX. You would have to use $.ajax and async:false
A better solution would be to call your function with a callback. This way, your code stays async, and continues when the json call is complete.
var WS = {
whoami: function (callback) {
$.getJSON("/SecurityData/GetCurrentUser", callback);
}
}
WS.whoami(function(data) {
// code that uses var data
});