I want to call an endpoint using Axios, then do something with the response data and a variable that was defined before the call. I know how to pass data into the callback when creating the function returning the promise myself, but since Axios is external code and I can't seem to find any option to pass extra data in the Axios docs or the config. Is there any other way to make this work, other than sending the value as data to the server and have the server respond it within the response or using an ugly workaround using the window global variable?
const animate = true; // The variable to be passed
axios.get('/endpoint.json')
.then(function(response, animate) { // This doesn't work...
if (response.data.coordinates) {
console.log(animate); // ...because this is undefined
setLocation('takeoff_location', response.data.coordinates, animate); // variable and response data need to be passed to this call
}
});
This creates a new animate variable, entirely unrelated to the one already defined:
function(response, animate)
This new variable "shadows" the one in the higher scope, so within this function any reference to animate only references this new variable. Which isn't defined because Axios (specifically the Promise returned by .get()) has no reason to pass a second value to this callback.
Don't shadow the variable:
function(response)
Then within the function any references to animate will reference the variable in the higher scope.
the .then() is a promise and has access to any variables outside the scope (like a closure). If you want to encapsulate the API call then wrap the entire thing in a function and the arguments go there.
const doRequest = function(animate) {
axios.get('/endpoint.json').then(function(response) {
if (response.data.coordinates) {
setLocation('takeoff_location', response.data.coordinates, animate);
}
});
};
doRequest(250);
doRequest(500);
Note that this will do 2 animation requests quickly in succession. If you want to "wait" for the other one to finish first, you would do this: (note that I'm now returning the axios request from the function). This means the promise object is returned and now you can chain more .then() functions on the "outside" of the function, which will happen only when the request finishes.
const doRequest = function(animate) {
return axios.get('/endpoint.json').then(function(response) {
if (response.data.coordinates) {
setLocation('takeoff_location', response.data.coordinates, animate);
}
});
};
doRequest(250).then(function() {
// this will happen when the outer API call finishes
doRequest(500);
});
Related
Say you have the following three functions and variable
var someList = [];
makeObject() {
// loops through someList here to create an object
// then calls sendObject function
sendObject()
}
sendObject() {
// sends object to database using HTTP call
}
resetList() {
// resets the list to be empty
// e.g. someList = []
}
Then you call them like so
makeObject()
resetList()
Is there any possiblity or any situation that the list will be reset before the makeObject function has a chance to loop through it?
There are plenty of things you can do in JavaScript which are asynchronous and non-blocking (XMLHttpRequest and setTimeout are classic examples). If you use any of those inside makeObject then resetList will run before the asynchronous parts get called.
resetList() will be called directly after the HTTP call is made. Unless you do other async work before the HTTP call, the order will always be:
makeObject()
loop inside makeObject()
sendObject() is called from inside makeObject()
sendObject() does the HTTP call
resetList() gets triggered right after the HTTP call since that HTTP call is async.
The HTTP returns and any handlers attached to it are triggered.
But make sure that you don't do any other async work, else this will not apply.
I'm using a third-party API which requires a callback function called callback_data(json) when the response comes back.
However, it seems to be calling callback_data(json) at the global scope. I would like to scope the callback_data(json) function within a block so that I can control the flow through promise.
Here's my attempt to do this:
return new Promise(resolve => {
(function(callback_data) {
api.request({"q": term}); //The third-party API call
})(function(json) {
resolve({...json.data, q: term})
});
}
);
So, the line api.request({"q": term}); will call callback_data(json) function when it receives the response from the server. But it calls the callback function at the global scope. I want it to call from within the block the api.request() was initiated.
What I did was I thought I could put everything into a self invoking function and pass in the callback_data(json) function as a parameter into the self invoking function. I thought this would scope everything within that self invoking function block. Unfortunately, this didn't work.
How can I do scope a callback function, which supposed to be at the global scope, to be called from within a block?
I'm using Typescript in my code.
If the API is really coded such that it issues:
callback_data(json);
...from within a scope you can't add functions to, there's nothing you can do. The API is badly designed (unless it's JSONP, in which case that's the only way it can work, but it should let you specify the name of the function) and callback_data must be global.
That doesn't mean that it has to be long-lived. You can declare it globally:
var callback_data = null; // At global scope
...and then in your code, assign a value to it and clear that value when done:
return new Promise(resolve => {
callback_data = function() { // Assign
callback_data = null; // Clear on callback
resolve({...json.data,
q: term
});
};
api.request({"q": term});
});
If it's JSONP and it lets you specify the callback name in the request, that would be better as you can create a different callback name for each request:
// Using `window` as JSONP is by definition in a browser
window.lastCallbackId = 0;
return new Promise(resolve => {
++lastCallbackId;
var name = "myCallback" + lastCallbackId;
window[name] = function() { // Assign, will be "myCallback1", "myCallback2", ...
window[name] = null; // Clear on callback
resolve({...json.data,
q: term
});
};
api.request({
"q": term,
callbackName: name // Note passing the name to it
});
});
I'm using PouchDB which is async and i'm new to async..
So if i get a Doc from pouchdb i have to use .then to see the actual result. Thats clear for me, but do i have to call .then every single time i want this value?
For example i made a SettingsService where i can store my settings key / value style.
// Get Setting
settingsService.get = function(key) {
return settingDB.get(key).catch(function (err) {
console.log(err);
});
};
When i wanna get the setting do i have to make every time this big .then block?
// Get Time of Last Sync
settingsService.get('lastSync').then(function (setting) {
$scope.lastSync = setting.value; // now it's assigned isn't it?
console.log(setting);
});
//edit, but this is still is undefined.. so again use then?
console.log($scope.lastSync);
Now what if i want the same setting again little bit later in the code, do i everytime to repeat this .then stuff?
Yes, you would require then if you're trying to access the value anywhere outside of current then block.
// Get Time of Last Sync
settingsService.get('lastSync').then(function (setting) {
$scope.lastSync = setting.value;
console.log(setting);
// inside the then function, you can access the $scope.lastSync variable directly
});
Anything you want to do with respect to $scope.lastSync must be inside this function. Else you can even use $scope.$watch(lastSync, function(newValue){}) to process something outside the then function. This will make sure that it will run after the value has changed and it's again similar to then.
But outside the then, you must use then because you won't know when the value will resolve.
So console.log($scope.lastSync); anywhere outside the then will give you undefined. You can use $timeout to do console.log but you can't guarantee when the value will be available. It's totally depended on the service call.
Though you've assigned it inside the then, then would execute at some point of time when the service call succeeds. But you're printing the console.log for $scope.lastSync immediately. So it will be still be undefined because the then callback has not executed yet.
In this example from Angularjs Docs there is some *magic*, which i can't figure out.
Here is a code:
var User = $resource('/user/:userId', {userId:'#id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
And one thing confusing me - how we can refer to user object inside callback and get retrieved data, when it is necessary to populate user first. But to do so value should be returned from User.get().
Like that:
call User.get() → return from User.get() → call function() callback
But after return it is not possible to execute anything, right?
It is absolutely possible to return the user and still have not executed the callback, and this is what this code example is showing.
The implementation of User.get might be something like:
User.prototype.get = function(parameters, callback) {
var user = {
abc: false
}
// do other stuff to construct the object
someLongAsyncOperation(callback);
return user;
}
Here the user is returned, but there is still a callback to be completed. Then within that callback the user can be accessed via closure scope. someLongAsyncOperation could be anything that later calls callback, you could try it out yourself with something as simple as a setTimeout or Angular's $timeout.
Its not in my opinion a great pattern, and it might be better to include the created user as a parameter in the callback - but it would work.
User.get() returns a blank object and performs an async GET to /user/123. This empty object is assigned to the user variable in the function scope.
The rest of the function (if there is any) executes and the function returns.
When the async call returns, the get() method populates the existing blank object's properties with the properties of the object returned, so the user variable now has the results of the callback.
The get() method calls the callback you specify. It has access to the function's user variable that is now populated from the async call.
Just as I thought I understood JS scope...
Take this code:
function init() {
var page;
var pageName = $('body').data('page');
switch(pageName) {
// (there are more switch cases here normally...)
case 'pSearchLatest':
require(['searchresults'], function (SearchResults) {
page = new SearchResults().init();
console.log(page); // <- shows the object!
});
default:
break;
}
console.log(page); // <- undefined
return page;
}
See the console log comments. Why is the second returning undefined when the var page is declared outside of the scope of the switch statement?
EDIT
So I mistakenly thought this was a scope issue but it's down to the asynchronous nature of AMD.
How can I return the value of page in the require scope in the same method without a while loop checking for undefined?
EDIT 2
I did it like this:
function init(callback) {
case 'pSearchLatest':
require(['searchresults'], function (SearchResults) {
page = new SearchResults().init();
callback(page)
});
}
and in my page that calls the wrapping init() method:
new PageController().init(function(asyncViewController) {
App.view = asyncViewController;
});
You did understand scope correctly. The code in that inner function does indeed assign to the variable in the outer scope.
What you did not understand is asynchronous behaviour. The require function takes a callback function which will be invoked somewhen in the future - when the requested module is available. Yet, it does immediately return and control flow will lead to the console.log statement, which does print undefined - which is the value the page variable has at that time.
You should be able to recognise that since the undefined is logged first, and the log statement in the callback (with the object) executes later, even though it comes above in the code.
require is asynchronous, therefore your function did exit first and then processed callback function.
Two possible reasons... you didnt match the case. or the callback where the assignment of the value to page happens hasnt been executed yet. Im guessing its the latter since it would be readily apparent if you case was failing. Look into the documentation for whatever AMD library youre using and see how the execution of the function(s) passed to require are executed.
Also as a generaly rule try to avoid returning values determined by asynchronous calls like this. Instead use some kind of Observer pattern to subscribe to a particular event that can be triggered from different places.