Trying to get some help with this code block.
My script first looks for a specific folder and if it exists the pass the id of the folder to the google.picker.DocsUploadView(). When I hard-code the value of setParent to 'gdfid', everything works well. On the other hand, I need the code to be parameterized.
thanks in advance for any assistance
Pete
here's my code:
var gdfid;
function createPicker() {
if (pickerApiLoaded && oauthToken) {
gapi.client.drive.files.list({
"corpora": "user",
"spaces": "drive",
"fields": "files(id,name)",
"q": "name = 'myUploads"
}).then(function(response) {
console.log( response.result.files.length );
if (response.result.files.length > 0) {
console.log( response.result );
gdfid = response.result.files[0].id;
}
//alert('Folder ID: ' + gdfid);
});
var picker = new google.picker.PickerBuilder().
setTitle('Upload to myPratt Folder').
enableFeature(google.picker.Feature.MULTISELECT_ENABLED).
enableFeature(google.picker.Feature.NAV_HIDDEN).
addView(new google.picker.DocsUploadView().
setIncludeFolders(false).
setParent('gdfid')). //tried with and without quotes
setOAuthToken(oauthToken).
setDeveloperKey(developerKey).
setCallback(pickerCallback).
build();
picker.setVisible(true);
}
}
It's probably the .then{} promise code block. I've had lots of trouble with them. The problem is that the .then{} code block has a closed scope.
When you assign gdfid = response.result.files[0].id; it's assumed that it is changing the global variable. But it isn't. It's only creating a local version of gdfid.
I ran around in circles myself for ages trying to figure out how to save external state information from within a .then{} block. Any possible solutions I came up with, were invariably no better than the callback hell that promises were supposed to solve in the first place. I even had problems returning objects out from it. I think the problem is that a .then{} block needs to run from a returned promise. Promises are actually functions earmarked to run in the future. They are subject to scoping restrictions, because they cannot make assumptions about the state of code outside the function. And they only pass object/variables a certain way. Trying to assign globals or returning data the regular way, from inside the .then{} block, is fraught with problems. It will often leave you tearing your hair out.
Try refactoring your code into a function with async/await, and use a try-catch statement to capture promise fails (Note: The try-catch statement still suffers from the global variable isolation problem, but at least it seems to be solely within the catch block. This is only an issue when an error occurs). I find async await much cleaner and easier to understand, and the scoping of variables works more intuitively.
In your case you could rewrite the code thus:
function async createPicker() {
var gdfid;
if (pickerApiLoaded && oauthToken) {
try {
var response = await gapi.client.drive.files.list({
"corpora": "user",
"spaces": "drive",
"fields": "files(id,name)",
"q": "name = 'myUploads"
});
console.log( response.result.files.length );
if (response.result.files.length > 0) {
console.log( response.result );
gdfid = response.result.files[0].id;
}
//alert('Folder ID: ' + gdfid);
var picker = new google.picker.PickerBuilder()
.setTitle('Upload to myPratt Folder')
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.addView(new google.picker.DocsUploadView()
.setIncludeFolders(false)
.setParent(gdfid)) //tried with and without quotes
.setOAuthToken(oauthToken)
.setDeveloperKey(developerKey)
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
} catch (e) {
console.log("Error displaying file list");
}
};
}
The only real difference here, is the await in front of the gapi.client.drive.files
function forces the code to wait for a callback to assign the response variable. This is not too much of a slowdown issue when running single popup UI elements that the user interacts with.
The gdfid variable is no longer global. In fact you don't even need it. You could setParent directly from the response variable.
Related
So I think this is probably me mixing up sync/async code (Mainly because Cypress has told me so) but I have a function within a page object within Cypress that is searching for customer data. I need to use this data later on in my test case to confirm the values.
Here is my function:
searchCustomer(searchText: string) {
this.customerInput.type(searchText)
this.searchButton.click()
cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length > 0) {
{Click some drop downdowns }
return data < ----I think here is the problem
} else {
{Do other stuff }
}
})
}
and in my test case itself:
let customerData = searchAndSelectCustomerIfExist('Joe Schmoe')
//Do some stuff with customerData (Probably fill in some form fields and confirm values)
So You can see what I am trying to do, if we search and find a customer I need to store that data for my test case (so I can then run some cy.validate commands and check if the values exist/etc....)
Cypress basically told me I was wrong via the error message:
cy.then() failed because you are mixing up async and sync code.
In your callback function you invoked 1 or more cy commands but then
returned a synchronous value.
Cypress commands are asynchronous and it doesn't make sense to queue
cy commands and yet return a synchronous value.
You likely forgot to properly chain the cy commands using another
cy.then().
So obviously I am mixing up async/sync code. But since the return was within the .then() I was thinking this would work. But I assume in my test case that doesn't work since the commands run synchronously I assume?
Since you have Cypress commands inside the function, you need to return the chain and use .then() on the returned value.
Also you need to return something from the else branch that's not going to break the code that uses the method, e.g an empty array.
searchCustomer(searchText: string): Chainable<any[]> {
this.customerInput.type(searchText)
this.searchButton.click()
return cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length) {
{Click some drop downdowns }
return data
} else {
{Do other stuff }
return []
}
})
}
// using
searchCustomer('my-customer').then((data: any[]) => {
if (data.length) {
}
})
Finally "Click some drop downdowns" is asynchronous code, and you may get headaches calling that inside the search.
It would be better to do those actions after the result is passed back. That also makes your code cleaner (easier to understand) since searchCustomer() does only that, has no side effects.
you just need to add return before the cy.wait
here's a bare-bones example
it("test", () => {
function searchCustomer() {
return cy.wait(100).then(intercept => {
const data = {text: "my data"}
return data
})
}
const myCustomer = searchCustomer()
myCustomer.should("have.key", "text")
myCustomer.its("text").should("eq", "my data")
});
I'm dipping my toe into the waters of Axios and async/await at the same time, and am trying to understand something about the control flow. Is the following legitimate?
let loading=true;
(async() => {
let response = null;
try {
response = await axios.get('https://whatever.com/api');
} finally {
loading=false;
}
if(response){
//do something with response here
}
})();
That is, can I count on the request to have returned at the point I am accessing the response variable? I appreciate I could guarantee it is by moving it into the 'try' immediately after the axios get, but then I would have to have the loading=false line before it, as well as in 'finally' (or 'catch'). I need to ensure that loading is set to false before any further actions, whether the request succeeds or fails, and I don't want to repeat myself. Maybe there's a better way of doing this?
Edit
Now that you have changed the question, the previous solution will not be working correctly. The issue is that the code inside the IIFE will be executed after everything else is finished, so loading will never be set to false from the perspective of the outside code. (the other code will be executed, and thеn the IIFE. That's because of the event loop). Your best bet is to make the outside code async and await the axios promise.
If you provide the problem details I might be able to help you refactor it.
Previous answer
I need to ensure that loading is set to false before any further actions
Any code after the await is guaranteed to NOT be loading:
(async() => {
let response = await axios.get('https://whatever.com/api');
// the request is finished, the await guarantees that
})();
If you need error handling, you can wrap it in a try/catch:
(async() => {
try {
let response = await axios.get('https://whatever.com/api');
// definitely not loading
}
catch (e) {
// definitely not loading, but an error occurred
}
})();
I'm new to RxJS and trying to wrap my brain around how I should be writing my code. I'm trying to write a function that extends an existing http which returns an observable array of data. I'd like to then loop over the array and make an http request on each object and return the new array with the modified data.
Here's what I have so far:
private mapEligibilitiesToBulk(bulkWarranties: Observable<any[]>): Observable<IDevice[]> {
const warranties: IDevice[] = [];
bulkWarranties.subscribe((bulk: any[]) => {
for (let warranty of bulk) {
// Check if another device already has the information
const foundIndex = warranties.findIndex((extended: IDevice) => {
try {
return warranty.device.stockKeepingId.equals(extended.part.partNumber);
} catch (err) {
return false;
}
});
// Fetch the information if not
if (foundIndex > -1) {
warranty.eligibilityOptions = warranties[foundIndex];
} else {
this.getDevices(warranty.device.deviceId.serialNumber).subscribe((devices: IDevice[]) => {
warranty = devices[0];
}); // http request that returns an observable of IDevice
}
warranties.push(warranty);
}
});
return observableOf(warranties);
}
Currently, my code returns an observable array immediately, however, its empty and doesn't react the way I'd like. Any advice or recommended reading would be greatly appreciated!
Without knowing a lot more about your data and what would make sense, it is impossible to give you the exact code you would need. However, I made some assumptions and put together this StackBlitz to show one possible way to approach this. The big assumption here is that the data is groupable and what you are actually trying to achieve is making only a single http call for each unique warranty.device.stockKeepingId.
I offer this code as a starting point for you, in the hopes it gets you a little closer to what you are trying to achieve. From the StackBlitz, here is the relevant method:
public mapEligibilitiesToBulk(bulk: Warranty[]): Observable<IDevice[]> {
return from(bulk).pipe(
tap(warranty => console.log('in tap - warranty is ', warranty)),
groupBy(warranty => warranty.device.stockKeepingId),
mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], []))),
tap(group => console.log('in tap - group is ', group)),
concatMap(group => this.getDevices(group[0].device.deviceId.serialNumber)),
tap(device => console.log('in tap - got this device back from api: ', device)),
toArray()
)
}
A couple of things to note:
Be sure to open up the console to see the results.
I changed the first parameter to an array rather than an observable, assuming you need a complete array to start with. Let me know if you want this to extend an existing observable, that is quite simple to achieve.
I put in some tap()s so you can see what the code does at two of the important points.
In the StackBlitz currently the getDevices() returns the same thing for every call, I did this for simplicity in mocking, not because I believe it would function that way. :)
I'm new to ASYNC programming so please bear with me. I have a call to a web service API that can be unpredictably slow. On the front end, I can handle it with a "loading" lightbox or something. However, on the backend, I have my request:
var req = http.request( options, function(res) {
res.on('data', function(chunk) {
doStuff();
} );
res.on('end', function() {
doMoreStuff(); // This can take a while to get to.
return someInfo();
} );
} );
req.end();
All of this is in a makeRequest module. So should I pass my callback function into makeRequest and then have it run after the 'end' event? It seems like this can lead to a very long chained event structure.
So any help on how to structure this would be greatly appreciated.
note: the above is mostly pseudocode so if there are syntax errors, please understand that it's pseudocode
Yes, generally you would pass a callback into whatever function you have this in, and when 'end' is emitted, you should take the data that you collected in the request, and pass it to your callback.
I realize it's pseudocode and you may know, I just want to say it anyways. Remember that 'data' can be called more than once, and that 'return' in your end function won't do anything.
For an example of doing a request, you can look at my answer over here.
Why won't my ExpressJS properly execute a request command?
I've build a livesearch with the jQuery.ajax() method. On every keyup events it receives new result data from the server.
The problem is, when I'm typing very fast, e.g. "foobar" and the GET request of "fooba" requires more time than the "foobar" request, the results of "fooba" are shown.
To handle this with the timeout parameter is impossible, I think.
Has anyone an idea how to solve this?
You can store and .abort() the last request when starting a new one, like this:
var curSearch;
$("#myInput").keyup(function() {
if(curSearch) curSearch.abort(); //cancel previous search
curSearch = $.ajax({ ...ajax options... }); //start a new one, save a reference
});
The $.ajax() method returns the XmlHttpRequest object, so just hang onto it, and when you start the next search, abort the previous one.
Assign a unique, incrementing ID to each request, and only show them in incrementing order. Something like this:
var counter = 0, lastCounter = 0;
function doAjax() {
++counter;
jQuery.ajax(url, function (result) {
if (counter < lastCounter)
return;
lastCounter = counter;
processResult(result);
});
}
You should only start the search when the user hasn't typed anything for a while (500ms or so). This would prevent the problem you're having.
An excellent jQuery plugin which does just that is delayedObserver:
http://code.google.com/p/jquery-utils/wiki/DelayedObserver
Make it so each cancels the last. That might be too much cancellation, but when typing slows, it will trigger.
That seems like an intense amount of traffic to send an ajax request for every KeyUp event. You should wait for the user to stop typing - presumably that they are done, for at least a few 100 milliseconds.
What I would do is this:
var ajaxTimeout;
function doAjax() {
//Your actual ajax request code
}
function keyUpHandler() {
if (ajaxTimeout !== undefined)
clearTimeout(ajaxTimeout);
ajaxTimeout = setTimeout(doAjax, 200);
}
You may have to play with the actual timeout time, but this way works very well and does not require any other plugins.
Edit:
If you need to pass in parameters, create an inline function (closure).
...
var fun = function() { doAjax(params...) };
ajaxTimeout = setTimeout(fun, 200);
You will want some kind of an ajax queue such as:
http://plugins.jquery.com/project/ajaxqueue
or http://www.protofunc.com/scripts/jquery/ajaxManager/
EDIT:Another option, study the Autocomplete plug-in code and emulate that.(there are several Autocomplete as well as the one in jquery UI
OR just implement the Autocomplete if that serves your needs