How to break out of a sequence of asynchronous operations in a loop? - dojo

Following this example
Dojo FAQ: How can I sequence asynchronous operations?
function doNext(previousValue) {
var dfd = new Deferred();
// perform some async logic; resolve the promise
setTimeout(function () {
var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
dfd.resolve(previousValue + next);
}, 50);
return dfd.promise;
}
var promise = doNext('a');
for (var i = 0; i < 9; i++) {
promise = promise.then(doNext);
}
promise.then(function (finalResult) {
// 'doNext' will have been invoked 10 times, each
// invocation only occurring after the previous one completed
// 'finalResult' will be the value returned
// by the last invocation of 'doNext': 'abcdefghijk'
console.log(finalResult);
});
How do I break out of the loop - i.e. stop processing subsequent doNext function calls when doNext meets a certain criteria - for example stop when the next character is 'd' and return the computed value up to that point?
EDIT: so far I tried using deferred cancel() method, but it just kills the process, and returns nothing.
setTimeout(function () {
var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
if(previousValue + next == 'abc')
dfd.cancel('abc');
else
dfd.resolve(previousValue + next);
}, 50);

You could do it by check the value returned from the promise and decide whether to call the async request again or not. It is not like you add a break in the for loop. But the result will be what you desire.
All the 9 promise.then will be called but the doNext will not be called 9 times. Below is the snippet for the same.
for (var i = 0; i < 9; i++) {
promise = promise.then(function(val){
return val === "abcd" ? val : doNext(val);
});
}
You might think this is not existing the loop. That is because the loop would have completed before the callback function is called. But, instead of calling the async function the callback function will simply return the value. which causes the loop to finish quickly. Below is a link to JSBin where I have increased the timeout and you will see that, initially it will take more time till the desired result is returned and then exits quickly.
https://jsbin.com/qiwesecufi/edit?js,console,output
Another, place you can do the checking, is within the doNext function itself.
function doNext(previousValue) {
var dfd = new Deferred();
if(previousValue === "abcd")
return previousValue;
// perform some async logic; resolve the promise
setTimeout(function () {
var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
dfd.resolve(previousValue + next);
}, 1000);
return dfd.promise;
}
Hope this was helpful.

You should only use the reduce (or promise = promise.then(doNext)) loop approach when you always want to process all the items (or decide synchronously how many you need).
To loop an arbitrary number of times and break out at any step, recursion is the better approach:
function wait(t, v) {
var dfd = new Deferred();
// asynchronously resolve the promise
setTimeout(function () {
dfd.resolve(v);
}, t);
return dfd.promise;
}
function doNext(previousValue) {
var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
return wait(50, previousValue + next);
}
function loop(v, i) {
if (i <= 0) return when(v);
if (v == "abc") return when("abc");
return doNext(v).then(function(r) {
return loop(r, i-1);
});
}
loop('a', 9).then(function (finalResult) {
console.log(finalResult);
});

Related

Axios update value outside of then to break the loop

I'm still currently learning on using React Native.
What I'm trying to do is update the limit value to 1 so it would break the while loop, but I am not sure on how to execute it since I can't update the value from inside the .then() in Axios POST call.
Glad if anyone would point out any method to handle this. Thank you for your help.
var limit = 0;
while (limit == 0) {
running = running + 20;
console.log("restart");
postDataCalories = {
"query": `${running} minutes run and ${walking} minutes walking`,
"gender":"male",
// "nf_calories": 363.62,
"weight_kg":63.5,
"height_cm":167.64,
"age":30
};
console.log(`${running} minutes run and ${walking} minutes walking`);
axios.post('https://trackapi.nutritionix.com/v2/natural/exercise', postDataCalories, axiosConfig2)
.then((res3) => {
console.log("exercise RESPONSE RECEIVED: ", res3);
let caloriesFood = res2.data.foods[0].nf_calories;
let caloriesExercise = res3.data.exercises[0].nf_calories;
let caloriesDifferences = caloriesFood - caloriesExercise;
console.log("hi " + caloriesDifferences);
if (caloriesDifferences < 50){
console.log('done');
limit = 1;
} else {
console.log('nope');
}
})
}
That's right in your case you cannot break the while loop inside the then-function because that function is called at a different moment in time (they call it asynchronous).
There are two things you can do. If you have access to await/async in your environment you could rewrite it to:
async someFunction() {
var limit = 0;
var running = 1; // arbitrarily start at 1.
while (limit == 0) {
running = running + 20;
console.log("restart running " + running);
postDataCalories = {
"query": `${running} minutes run and ${walking} minutes walking`,
"gender":"male",
// "nf_calories": 363.62,
"weight_kg":63.5,
"height_cm":167.64,
"age":30
};
console.log(`${running} minutes run and ${walking} minutes walking`);
var res3 = await axios.post('https://trackapi.nutritionix.com/v2/natural/exercise', postDataCalories, axiosConfig2)
console.log("exercise RESPONSE RECEIVED: ", res3);
let caloriesFood = res2.data.foods[0].nf_calories;
let caloriesExercise = res3.data.exercises[0].nf_calories;
let caloriesDifferences = caloriesFood - caloriesExercise;
console.log("hi " + caloriesDifferences);
if (caloriesDifferences < 50){
console.log('done');
limit = 1;
// you may also do:
break;
} else {
console.log('nope');
}
}
}
}
For usual web conditions it requires either a modern browser (Firefox/Chrome) (or when you have babel / regenerator-runtime might do the trick, maybe your setup is already capable of transpiling this/running this.)
If you dont have access to async/await then you need to apply recursion (to work around the synchronicity). Normally you can perform the tasks sequentially (in a row, step by step, using a while loop), now you would write something like:
function runTheLoop(running, walking) {
postDataCalories = {
"query": `${running} minutes run and ${walking} minutes walking`,
"gender":"male",
// "nf_calories": 363.62,
"weight_kg":63.5,
"height_cm":167.64,
"age":30
};
console.log(`${running} minutes run and ${walking} minutes walking`);
axios.post('https://trackapi.nutritionix.com/v2/natural/exercise', postDataCalories, axiosConfig2)
.then((res3) => {
console.log("exercise RESPONSE RECEIVED: ", res3);
let caloriesFood = res2.data.foods[0].nf_calories;
let caloriesExercise = res3.data.exercises[0].nf_calories;
let caloriesDifferences = caloriesFood - caloriesExercise;
console.log("hi " + caloriesDifferences);
if (caloriesDifferences < 50){
console.log('done');
// limit = 1;
return;
} else {
console.log('nope');
// This is the recursive variant of "running the loop again"
return runTheLoop(running + 20, walking + 20);
}
})
}
}
// Somewhere:
console.log("restart");
// one minute of walking and one minute of running.
runTheLoop(1, 1);
Note: I've used your your code to make the examples relevant in to your situation, I could not test it out myself so it may not work directly if you copy and paste this.

$nextTick running before previous line finished

I have a vue function call which is triggered when selecting a radio button but it seems that my code inside my $nextTick is running before my previous line of code is finished. I don't want to use setTimout as I don't know how fast the user connection speed is.
findOrderer() {
axios.post('/MY/ENDPOINT')
.then((response) => {
this.orderers = response.data.accounts;
console.log('FIND_ORDER', this.orderers)
...OTHER_CODE
}
rbSelected(value) {
this.findOrderer();
this.newOrderList = [];
this.$nextTick(() => {
for (var i = 0, length = this.orderers.length; i < length; i++) {
console.log('FOR')
if (value.srcElement.value === this.orderers[i].accountType) {
console.log('IF')
this.newOrderList.push(this.orderers[i]);
}
}
this.$nextTick(() => {
this.orderers = [];
this.orderers = this.newOrderList;
console.log('orderers',this.orderers)
})
})
}
Looking at the console log the 'FINE_ORDERER' console.log is inside the 'findOrderer' function call so I would have expected this to be on top or am I miss using the $nextTick
That's expected, since findOrderer() contains asynchronous code. An easy way is to simply return the promise from the method, and then await it instead of waiting for next tick:
findOrderer() {
return axios.post('/MY/ENDPOINT')
.then((response) => {
this.orderers = response.data.accounts;
console.log('FIND_ORDER', this.orderers);
});
},
rbSelected: async function(value) {
// Wait for async operation to complete first!
await this.findOrderer();
this.newOrderList = [];
for (var i = 0, length = this.orderers.length; i < length; i++) {
console.log('FOR')
if (value.srcElement.value === this.orderers[i].accountType) {
console.log('IF')
this.newOrderList.push(this.orderers[i]);
}
}
this.orderers = [];
this.orderers = this.newOrderList;
console.log('orderers',this.orderers)
}

Is there a way to wait until a function is finished in React Native?

I'm trying to get information (true/false) from AsyncStorage in a function and create a string which is importent to fetch data in the next step. My problem is, the function is not finished until the string is required.
I tried many solutions from the internet like async function and await getItem or .done() or .then(), but none worked out for me.
//_getFetchData()
AsyncStorage.getAllKeys().then((result) => { //get all stored Keys
valuelength = result.length;
if (valuelength !== 0) {
for (let i = 0; i < valuelength; i++) {
if (result[i].includes("not") == false) { //get Keys without not
AsyncStorage.getItem(result[i]).then((resultvalue) => {
if (resultvalue === 'true') {
if (this.state.firstValue) {
this.state.channels = this.state.channels + "channel_id" + result[i];
console.log("channel: " + this.state.channels);
}
else {
this.state.channels = this.state.channels + "channel" + result[i];
}
}
});
}
return this.state.channels;
_fetchData() {
var channel = this._getFetchData();
console.log("channel required: " + channel);
}
The current behaviour is that the console displays first "channel required: " than "channel: channel_id0".
Aspects in your question are unclear:
You don't say when this.state.firstValue is set, and how that relates to what you are trying to accomplish.
You have a for-loop where you could be setting the same value multiple times.
You mutate the state rather than set it. This is not good, see this SO question for more on that.
There are somethings we can do to make your code easier to understand. Below I will show a possible refactor. Explaining what I am doing at each step. I am using async/await because it can lead to much tidier and easier to read code, rather than using promises where you can get lost in callbacks.
Get all the keys from AsyncStorage
Make sure that there is a value for all the keys.
Filter the keys so that we only include the ones that do not contain the string 'not'.
Use a Promise.all, this part is important as it basically gets all the values for each of the keys that we just found and puts them into an array called items
Each object in the items array has a key and a value property.
We then filter the items so that only the ones with a item.value === 'true' remain.
We then filter the items so that only the ones with a item.value !== 'true' remain. (this may be optional it is really dependent on what you want to do)
What do we return? You need to add that part.
Here is the refactor:
_getFetchData = async () => {
let allKeys = await AsyncStorage.getAllKeys(); // 1
if (allKeys.length) { // 2
let filteredKeys = allKeys.filter(key => !key.includes('not')); // 3
let items = await Promise.all(filteredKeys.map(async key => { // 4
let value = await AsyncStorage.getItem(key);
return { key, value }; // 5
}))
let filteredTrueItems = items.filter(item => items.value === 'true'); // 6
let filteredFalseItems = items.filter(item => items.value !== 'true'); // 7
// now you have two arrays one with the items that have the true values
// and one with the items that have the false values
// at this points you can decide what to return as it is not
// that clear from your question
// return the value that your want // 8
} else {
// return your default value if there are no keys // 8
}
}
You would call this function as follows:
_fetchData = async () => {
let channel = await this._getFetchData();
console.log("channel required: " + channel);
}
Although the above will work, it will not currently return a value as you haven't made it clear which value you wish to return. I would suggest you build upon the code that I have written here and update it so that it returns the values that you want.
Further reading
For further reading I would suggest these awesome articles by Michael Chan that discuss state
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
I would also suggest taking some time to read up about async/await and promises
https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
And finally this article and SO question on Promise.all are quite good
https://www.taniarascia.com/promise-all-with-async-await/
Using async/await with a forEach loop
Try this instead. Async functions and Promises can be tricky to get right and can be difficult to debug but you're on the right track.
async _getFetchData() {
let channels = "";
let results = await AsyncStorage.getAllKeys();
results.forEach((result) => {
if (result.includes("not") === false) {
let item = await AsyncStorage.getItem(result);
if (item === 'true') {
console.log(`channel: ${result}`)
channels = `channel_id ${result}`;
}
}
});
return channels;
}
_fetchData() {
this._getFetchData().then((channels) => {
console.log(`channel required: ${channel}`);
});
}
what if you wrap the _getFetchData() in a Promise? This would enable you to use
var channel = this._getFetchData().then(console.log("channel required: " + channel));
Otherwise the console.log won't wait for the execution of the _getFetchData().
This is what the console.log is telling you. it just logs the string. the variable is added after the async operation is done.
UPDATE
I would try this:
//_getFetchData()
AsyncStorage.getAllKeys().then((result) => { //get all stored Keys
valuelength = result.length;
if (valuelength !== 0) {
for (let i = 0; i < valuelength; i++) {
if (result[i].includes("not") == false) { //get Keys without not
AsyncStorage.getItem(result[i]).then((resultvalue) => {
if (resultvalue === 'true') {
if (this.state.firstValue) {
this.state.channels = this.state.channels + "channel_id" + result[i];
console.log("channel: " + this.state.channels);
}
else {
this.state.channels = this.state.channels + "channel" + result[i];
}
}
});
}
return new Promise((resolve, reject) => {
this.state.channels !=== undefined ? resolve(this.state.channels) : reject(Error('error '));
}
_fetchData() {
var channel = this._getFetchData().then(console.log("channel required: " + channel));
}
maybe you must change the this.state.channels !=== undefined to an expression that's matches the default value of this.state.channels.

QZ TRAY PRINITING ORDER NOT IN SEQ

I'm trying to print qz tray from javascript.
I have barcode with number in ascending order 1,2,3,4, 5 and so on.
I looping the seq correctly . but when printed out, it was not in order.
setTimeout("directPrint2()",1000);
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
function directPrint2(){
var data;
var xhttp;
var v_carton = "' || x_str_carton ||'";
var carton_arr = v_carton.split('','');
var v1 = "' ||
replace(x_zebra_printer_id, '\', '|') ||
'".replace(/\|/g,"\\");
if(v1 == ""){
alert("Please setup ZPL Printer");
}
else{
xhttp=new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
data = [ toNative(this.responseText) ];
printZPL(data, v1);
}
};
for (var j = 0; j < carton_arr.length; j++){
var url = "' || x_wms_url ||
'WWW_URL.direct_print_label?in_carton_no="+toValidStr(carton_arr[j]);
xhttp.open("GET", url, false);
xhttp.send();
sleep(5000);
}
}
};
',
'javascript'
What's missing from your example:
I do not see any looping logic in the example calling the printZPL function,
printZPL isn't a QZ Tray function and you're missing the code snippet which it calls. Usually this would be qz.print(config, data);.
Regardless of the missing information, the qz.print(...) API is ES6/Promise/A+ based meaning if you want to call qz.print multiple times in a row you need to use a Promise-compatible technique. (e.g. .then(...) syntax) between your print calls as explained in the Chaining Requests guide.
To avoid this, you can concatenate all ZPL data into one large data array. Be careful not to spool too much data at once.
If you know exactly how many jobs you'll be appending, you can hard-code the promise chain:
qz.websocket.connect()
.then(function() {
return qz.printers.find("zebra"); // Pass the printer name into the next Promise
})
.then(function(printer) {
var config = qz.configs.create(printer); // Create a default config for the found printer
var data = ['^XA^FO50,50^ADN,36,20^FDRAW ZPL EXAMPLE^FS^XZ']; // Raw ZPL
return qz.print(config, data);
})
.catch(function(e) { console.error(e); });
Finally, if you do NOT know in advanced how many calls to qz.print(...) you can use a Promise loop as explained in the Promise Loop guide.
function promiseLoop() {
var data = [
"^XA\n^FO50,50^ADN,36,20^FDPRINT 1 ^FS\n^XZ\n",
"^XA\n^FO50,50^ADN,36,20^FDPRINT 2 ^FS\n^XZ\n",
"^XA\n^FO50,50^ADN,36,20^FDPRINT 3 ^FS\n^XZ\n",
"^XA\n^FO50,50^ADN,36,20^FDPRINT 4 ^FS\n^XZ\n"
];
var configs = [
{ "printer": "ZDesigner LP2844-Z" },
{ "printer": "ZDesigner LP2844-Z" },
{ "printer": "ZDesigner LP2844-Z" },
{ "printer": "ZDesigner LP2844-Z" }
];
var chain = [];
for(var i = 0; i < data.length; i++) {
(function(i_) {
//setup this chain link
var link = function() {
return qz.printers.find(configs[i_].printer).then(function(found) {
return qz.print(qz.configs.create(found), [data[i_]]);
});
};
chain.push(link);
})(i);
//closure ensures this promise's concept of `i` doesn't change
}
//can be .connect or `Promise.resolve()`, etc
var firstLink = new RSVP.Promise(function(r, e) { r(); });
var lastLink = null;
chain.reduce(function(sequence, link) {
lastLink = sequence.then(link);
return lastLink;
}, firstLink);
//this will be the very last link in the chain
lastLink.catch(function(err) {
console.error(err);
});
}
Note: The Promise Loop is no longer needed in QZ Tray 2.1. Instead, since 2.1, an array of config objects and data arrays can be provided instead.

How to send the index of a for loop into the promise of a function in a Vue Resource call?

I am looping through an object however in the asynchronous part the i variable is always five.
How can I maintain that value, or pass it into the function
getProductData: function() {
var vm = this;
for (var i = 0; i < vm.recommendationResponse['recommendedItems'].length; i++) {
var sku = vm.recommendationResponse['recommendedItems'][i]['items'][0]['id'];
vm.$http.get('http://127.0.0.1:8000/models/api/productimage/' + sku).then(response => {
// get body data
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = response.body['product_image_url'];
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = response.body['price'];
}, response => {
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = '';
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = '';
});
}
}
I I do something like this:
vm.$http.get('http://127.0.0.1:8000/models/api/productimage/' + sku).then((response, i) => ...
then i is undefined
Who do I keep the index of the loop or should I go about it a different way?
Always use let to initialize variables in for loop when dealing with async operations. Similar things goes to having for loops in intervals. By using let you make sure you always have a unique variable assigned to i.
for (let i = 0, recommendationlength = vm.recommendationResponse['recommendedItems'].length; i < recommendationlength; i++)
Little bonus, if you cache array length in the beginning you get a small performance boost :-)
You could use Array.prototype.forEach instead:
var vm = this;
vm.recommendataionResponse['recommendedItems'].forEach((item, i) => {
var sku = vm.recommendationResponse['recommendedItems'][i]['items'][0]['id'];
vm.$http.get('http://127.0.0.1:8000/models/api/productimage/' + sku).then(response => {
// get body data
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = response.body['product_image_url'];
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = response.body['price'];
}, response => {
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = '';
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = '';
});
})
This way, since there is a unique scope for each i value, each .then callback will be able to reference the correct value.