I want to call ajax request 3 times sequentially (requests have same URL) as describe:
Request 1 -> Done -> Request 2 -> Done -> Request 3 -> Done -> Do something
In each callback functions, if have any conditions is correct, the loop will be end.
I can write simple script to do, like:
$.ajax().then(function () {
if (condition) {}
else {
$.ajax().then(function () {
if () else () {
$.ajax().then(function () {
if () else ();
});
}
});
}
});
But if its not only 3 times (ex n times), it looks so so stupid.
How I make many requests using Promise?
Thanks a lots!
Using vanilla promises, a "recursive function" would work. Simply supply the number of times (plus one) on each recursive call.. the recursive function will itself return a new promise if there is additional work to be done.
var maxCalls = 3
function makeCall(timesCalled) {
// we're done - don't return a chained promise
if (timesCalled > maxCalls)
return;
return $.ajax().then(function () {
// likewise - don't return chained promise
// (it may make sense to move this up outside)
if (condition)
return;
// return the next promise, or not if done
return makeCall(timesCalled + 1);
});
}
var promiseResolvedAtEnd = makeCall(1);
Classic JavaScript problem.
You can look into "generators" with yield keyword.
It's a new feature in EcmaScript 6, made to make asynchronous calls look/feel synchronous.
That's exactly what avoids the "Ajax Christmas-tree" you've stumbled upon, without breaking asynchronous nature of your calls.
Now they might look a little convoluted at first. But once you get into it, it should become second nature. Imagine never having to deal with the recursive web of $.ajax events again.
Note: I myself don't fully understand them yet. But in my experiments so far I got the code you're seeing below. I think you have to call wrapper eventually by passing generator to it. If anyone else can shed more light on this, it would be appreciated.
Here is an example:
<script>
// -- I: Basic Example
function generator()
{
yield 0 + 1;
yield 0 + 2;
yield 0 + 3;
yield "Hello";
return "Done.";
}
var gen = generator();
// Iterate through return values until done
gen.next(); // 1
gen.next(); // 2
gen.next(); // 3
gen.next(); // "Hello."
gen.next(); // "Done."
// -- II: Take this principle to a real-case scenario:
window.onload = function() {
// Generator - asynchronous code that looks synchronous
function generator() {
var tweets = yield $.getJSON("data/tweets.json");
var friends = yield $.getJSON("data/friends.json");
var followers = yield $.getJSON("data/followers.json");
}
// Wrap
function wrapper(generator) {
var gen = generator();
// Handle
function handle(yielded) {
if (!yielded.done) {
// Then
yielded.value.then(function(data) {
// Next
return handle(gen.next(data));
});
}
}
}
}
</script>
Related
How does work app.use in KOA?
When I set some generator inside app.use, everything works perfect.
How can I do the same elsewhere?
When I just execute generator manual:
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
}
console.log({'outside: ': getRelationsList().next()});
getRelationsList().next();
I'm getting just { 'outside: ': { value: [Function], done: false } }
This what I expect is:
{ 'outside: ': { value: {object_with_results}, done: false } }
{ 'inside: ': {object_with_results}
EDIT
I changed my code like that:
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
}
console.log({'outside ': co(getRelationsList)});
Now inside console log show's me good results but outside console log shows me just empty object.
Generators are a powerful tool for organizing asynchronous code, but they don't magically wait for asynchronous code to run.
What's Happening
Let's walk through your code so you can see what is happening:
getRelationsList is a generator function, that when called returns a new generator. At this point, no code in your generator function has been called (although if you were passing params they would be set). You then call .next on your generator to start execution of the generator function. It will execute up until it hits the first yield statement and return an object with the yielded value and the completion status of the generator.
It seems you understand most of that so far, but generators do not magically transform the yielded out values. When you yield out db.relations.find({}), you'll get the return value of the find function which I'm assuming is a Promise or some type of thenable:
so your 'outside' value is { value:Promise, done:false }
The reason your inside console.log never ran is that you're actually creating a new generator each time you call getRelationsList(), so when you call getRelationsList().next() again after the outside console.log you're creating a new generator and calling next, so it only executes up to the first yield, just like the call on the previous line.
In order to finish execution you must call next twice on the same instance of your generator: once to execute up to the yield and once to continue execution to the end of the function.
var gen = getRelationsList()
gen.next() // { value:Promise, done:false }
gen.next() // { value:undefined, done:true } (will also console.log inside)
You'll notice, however, if you run this, the inside console.log will be undefined. That's because the value of a yield statement is equal to the value passed to the following .next() call.
For example:
var gen2 = getRelationsList()
gen2.next() // { value:Promise, done:false }
gen2.next(100) // { value:undefined, done:true }
Outputs
{ inside:100 }
Because we passed 100 to the second .next() call and that became the value of the yield db.relations.find({}) statement which was then assigned to res.
Here's a link demoing all of this: http://jsfiddle.net/qj1aszub/2/
The Solution
The creators of koa use a little library called co which basically takes yielded out promises and waits for them to complete before passing the resolved value back into the generator function (using the .next() function) so that you can write your asynchronous code in a synchronous style.
co will return a promise, which will require you to call the .then method on to get the value returned from the generator function.
var co = require('co');
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
return res
}
co(getRelationsList).then(function(res) {
console.log({'outside: ': res })
}).catch(function(err){
console.log('something went wrong')
});
co also allows you to yield out other generator functions and wait for their completion, so you don't have to wrap things with co at every level and deal with promises until you're at some sort of 'top level':
co(function *() {
var list = yield getRelationsList()
, processed = yield processRelations(list)
, response = yield request.post('/some/api', { data:processed })
return reponse.data
}).then(function(data) {
console.log('got:', data)
})
Your problem is that you call getRelationsList() function multiple times which is incorrect.
Change your code to following
var g = getRelationsList();
console.log('outside: ', g.next());
g.next(); //You get your console.log('inside: .... here
Generators must be acted upon by outside code.
Under the hood koa use the co library to 'Run' the generator.
Here is how you might achieve what your wanting outside of koa:
var co = require('co');
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
}
co(getRelationsList).catch(function(err){});
I did a short screencast on JavaScript generators that should help you understand what's going on:
http://knowthen.com/episode-2-understanding-javascript-generators/
++ EDIT
If your using generators to program in more of an synchronous style (eliminating callbacks), then all your work needs to be done in the generator and you should use a library like co to execute the generator.
Here is a more detailed example of how you would interact with a generator, manually. This should help you understand the results your getting.
function * myGenerator () {
var a = yield 'some value';
return a;
}
var iterator = myGenerator();
// above line just instantiates the generator
console.log(iterator);
// empty object returned
// {}
var res1 = iterator.next();
// calling next() start the generator to either the
// first yield statement or to return.
console.log(res1);
// res1 is an object with 2 attributes
// { value: 'some value', done: false }
// value is whatever value was to the right of the first
// yield statment
// done is an indication that the generator hasn't run
// to completion... ie there is more to do
var toReturn = 'Yield returned: ' + res1.value;
var res2 = iterator.next(toReturn);
// calling next(toReturn) passes the value of
// the variable toReturn as the return of the yield
// so it's returned to the variable a in the generator
console.log(res2);
// res2 is an object with 2 attributes
// { value: 'Yield returned: some value', done: true }
// no further yield statements so the 'value' is whatever
// is returned by the generator.
// since the generator was run to completion
// done is returned as true
I've been reading about setTimeout and other such timers. But I'm wondering if it's possible to work up a custom function so that all you would need to do is something like this:
//code
delay(time);
//more code
Is this possible?
UPDATE: Ok, I kind of get it. So if that isn't reasonably possible, how would you go about delaying a loop AFTER the first time. I want it to run immediately upon execution but they delay on each iteration afterward.
New UPDATE: I figure since my initial thought fell through, it might just be easier to show you the code I have.
function autoFarm (clickEvent){
var farmTargets = [
"6_300_1",
"6_300_3",
"6_300_4",
"6_300_5",
"6_300_7"];
setTimeout(function() {
$.each (farmTargets, function(index, target){
var extraData = '{"end_pos":"' + target + '","purpose":0,"upshift":1,"bring_res":{"0":0,"2":0,"1":0},"bring_ship":{"1":25,"11":0},"rate":100,"start_pos":"6_300_2"}';
var finalData = baseDataDora + extraData + "&type=1";
setTimeout(function(){
for (i = 0; i < farmTargets.length; i++){
postRequest(sendFleetURL + getSign(extraData). finalData, function(json){
});
}
}, 15000);
});//End each loop
}, 1320000);
}//End autoFarm
Basically, it should execute immediately and run the for loop 5 times on the first array element 15 seconds apart. Then 22 minutes later move to the next set and repeat for the entire array.
You can achieve something along those lines with generators. The idea is that continuation passing style (callback hell) can be flattened. The generator uses the yield keyword to pause the function, until the callback resumes it by calling its next method:
var async = function(gen) {
var g = gen()
function next(x) {
var cur = g.next(x)
if (cur.done) {
return cur.value
}
cur.value(next)
}
next()
}
var delay = function(time) {
return function(f) {
setTimeout(f, time)
}
}
async(function* () {
console.log('before')
yield delay(1000) // waits one second
console.log('middle')
yield delay(1000) // waits one second
console.log('after')
})
In CPS it would read something like:
console.log('before')
setTimeout(function() {
console.log('middle')
setTimeout(function() {
console.log('after')
}, 1000)
}, 1000)
This works in Chrome, Firefox and iojs today.
This isn't possible because of the way single-threaded event loops work. If this function were to exist, it would cause the entire UI thread to freeze until the delay was satisfied. setTimeout(cb, delay) is the nearest facility which schedules a function to be executed no earlier than the delay and at the end of the current event loop tick.
Update: Before somebody calls me on it, yes, you can theoretically engineer a delay function that freezes everything in place for a set amount of time. However, there is no reasonable excuse to do it this way.
To your second question:
function hello() {
console.log('hello');
}
// execute immediately
hello();
// then every 5 seconds
setInterval(hello, 5000);
As-written, no that's not possible.
If, instead you were to use a queue, delays in that manner are trivial.
jQuery's .queue() and .delay() functions are a good example of how this works, so I will use them as an example, however the general point stands for any queueing library.
Instead of:
//code
delay(time)
//more code
With a queue, you'd write:
$('...') //some selector to act on for jQuery
.queue(function (next) {
//code
//Indicate that the queued call is finished.
next();
//This allows async code to be executed in the queue,
//such as ajax and animations
})
.delay(time)
.queue(function (next) {
//more code
next();
});
Now, even if you ignore the lines used for comments, you can tell that there's a bit more boilerplate to achieve the desired behavior. I don't feel that it's excessive, because I find it relatively easy to read:
queue something to happen
wait for some number of milliseconds
queue something else to happen
Using a Promise, calling it inside an asynchronous function.
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const any_function = async() => {
await delay(2000);
console.log('this log has been delayed 2 secs')
}
I've read over several examples of code using JavaScript generators such as this one. The simplest generator-using block I can come up with is something like:
function read(path) {
return function (done) {
fs.readFile(path, "file", done);
}
}
co(function *() {
console.log( yield read("file") );
})();
This does indeed print out the contents of file, but my hangup is where done is called. Seemingly, yield is syntactic sugar for wrapping what it returns to in a callback and assigning the result value appropriately (and at least in the case of co, throwing the error argument to the callback). Is my understanding of the syntax correct?
What does done look like when yield is used?
Seemingly, yield is syntactic sugar for wrapping what it returns to in a callback and assigning the result value appropriately (and at least in the case of co, throwing the error argument to the callback)
No, yield is no syntactic sugar. It's the core syntax element of generators. When that generator is instantiated, you can run it (by calling .next() on it), and that will return the value that was returned or yielded. When the generator was yielded, you can continue it later by calling .next() again. The arguments to next will be the value that the yield expresion returns inside the generator.
Only in case of co, those async callback things (and other things) are handled "appropriately" for what you would consider natural in an async control flow library.
What does done look like when yield is used?
The thread function example from the article that you read gives you a good impression of this:
function thread(fn) {
var gen = fn();
function next(err, res) {
var ret = gen.next(res);
if (ret.done) return;
ret.value(next);
}
next();
}
In your code, yield does yield the value of the expression read("file") from the generator when it is ran. This becomes the ret.val, the result of gen.next(). To this, the next function is passed - a callback that will continue the generator with the result that was passed to it. In your generator code, it looks as if the yield expression returned this value.
An "unrolled" version of what happens could be written like this:
function fn*() {
console.log( yield function (done) {
fs.readFile("filepath", "file", done);
} );
}
var gen = fn();
var ret1 = gen.next();
var callasync = ret1.value;
callasync(function next(err, res) {
var ret2 = gen.next(res); // this now does log the value
ret2.done; // true now
});
I posted a detailed explanation of how generators work here.
In a simplified form, your code might look like this without co (untested):
function workAsync(fileName)
{
// async logic
var worker = (function* () {
function read(path) {
return function (done) {
fs.readFile(path, "file", done);
}
}
console.log(yield read(fileName));
})();
// driver
function nextStep(err, result) {
try {
var item = err?
worker.throw(err):
worker.next(result);
if (item.done)
return;
item.value(nextStep);
}
catch(ex) {
console.log(ex.message);
return;
}
}
// first step
nextStep();
}
workAsync("file");
The driver part of workAsync asynchronously iterates through the generator object, by calling nextStep().
I'm a bit confused as to how setTimeout works. I'm trying to have a setTimeout in a loop, so that the loop iterations are, say, 1s apart.
Each loop iteration makes an HTTP request and it seems like the server on the other end can't handle that many requests in such a short time span.
for (var i = 1; i<=2000 && ok; i++) {
var options = {
host:'www.host.com',
path:'/path/'+i
};
setTimeout(makeRequest(options, i), 1000);
};
Why does this not work and how can I achieve this?
Thank you
setTimeout is non blocking, it is asynchronous. You give it a callback and when the delay is over, your callback is called.
Here are some implementations:
Using recursion
You can use a recursive call in the setTimeout callback.
function waitAndDo(times) {
if(times < 1) {
return;
}
setTimeout(function() {
// Do something here
console.log('Doing a request');
waitAndDo(times-1);
}, 1000);
}
Here is how to use your function:
waitAndDo(2000); // Do it 2000 times
About stack overflow errors: setTimeout clear the call stack (see this question) so you don't have to worry about stack overflow on setTimeout recursive calls.
Using generators (io.js, ES6)
If you are already using io.js (the "next" Node.js that uses ES6) you can solve your problem without recursion with an elegant solution:
function* waitAndDo(times) {
for(var i=0; i<times; i++) {
// Sleep
yield function(callback) {
setTimeout(callback, 1000);
}
// Do something here
console.log('Doing a request');
}
}
Here is how to use your function (with co):
var co = require('co');
co(function* () {
yield waitAndDo(10);
});
BTW: This is really using a loop ;)
Generator functions documentation.
You need something like this
var counter = 5;
function makeRequst(options, i) {
// do your request here
}
function myFunction() {
alert(counter);
// create options object here
//var options = {
// host:'www.host.com',
// path:'/path/'+counter
//};
//makeRequest(options, counter);
counter--;
if (counter > 0) {
setTimeout(myFunction, 1000);
}
}
See also this fiddle
At the point of the alert(count); you can do your call to the server.
Note that the counter works opposite (counting down). I updated with some
comments where to do your thing.
Right now you're scheduling all of your requests to happen at the same time, just a second after the script runs. You'll need to do something like the following:
var numRequests = 2000,
cur = 1;
function scheduleRequest() {
if (cur > numRequests) return;
makeRequest({
host: 'www.host.com',
path: '/path/' + cur
}, cur);
cur++;
setTimeout(scheduleRequest, 1000)
}
Note that each subsequent request is only scheduled after the current one completes.
I might be late at the party but here is another (more readable) solution without the need to omit for loop.
What your code does is creating 2000 (actually 1999) setTimeout objects that will call the makeRequest function after 1 second from now. See, none of them knows about the existence of the other setTimeouts.
If you want them 1 sec apart from each other, you are responsible for creating them so.
This can be achieve by using your counter (in this case i) and the timeout delay.
for (var i = 1; i<=2000 && ok; i++) {
var options = {
host:'www.host.com',
path:'/path/'+i
};
setTimeout(makeRequest(options, i), i * 1000); //Note i * 1000
};
The first timeout object will be set for 1 second from now and the second one will be set for 2 seconds from now and so on; Meaning 1 second apart from each other.
I'm surprised that no one has mentioned this above, but it sounds like you need setInterval not setTimeout.
vat poller = setInterval(makeRequestFunc, 3000)
The code above will make a request every 3 seconds. Since you saved the object to the variable poller, you can stop polling by clearing the object like so:
cleanInterval(poller)
You're calling makeRequest() in your setTimeout call - you should be passing the function to setTimeout, not calling it, so something like
setTimeout(makeRequest, 1000);
without the ()
let i = 20;
let p = Promise.resolve(i)
while (i > 0) {
(i => {
p = p.then(() => {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(i);
resolve()
}, 2000)
})
})
})(i)
i--
}
p = p.then(data => console.log('execution ends'))
I'm very late on the subject (as usual ...;) but the only way I found to loop requests to a slow time response API and getting responses without HTTP 504 is using promises.
async function LoadDataFromAPI(myParametersArray) {
for(var i = 0; i < myParametersArray.length; i++) {
var x = await RunOneRequest(myParametersArray[i]);
console.log(x); // ok
}
}
The function called by the async function :
function RunOneRequest(parameter) {
return new Promise(resolve => {
setTimeout(() => {
request(parameter, (error, response, body) => {
// your request
});
resolve('ok);
}, 2000); // 2 secs
});
}
I see there are lot's of threads here in SO about asking for a javascript sleep function and I know it can be done only using setTimeout and setInterval.
I do some userscripting with greasemonkey and written a script that loads a lot of pages and calculates something from them. It works, but I don't want to request the pages too fast.
var html0=syncGet(url0); // custom function for sync ajax call.
// fill the something array
for(var i=0;i<something.length;i++)
{
// calculate url1,url2 using the array and the i variable
// do something with lots of local variables
var html1=syncGet(url1);
// I would put a sleep here.
// do something with the results
var html2=syncGet(url2);
// I would put a sleep here.
// do something with the results
// get url3 from the page loaded from url2
var html3=syncGet(url3);
// I would put a sleep here.
// do something with the results
}
// use the result of the for loop and lots of code will follow...
The actual code is a bit more complex and longer than this.
I'm crying for the nonexistent sleep function (and understand why is it not possible) How to refactor this to use setTimeout, setInterval functions and keep it readable (and working) too?
For example this:
var urls = ["your","u","r","l´s"];
var htmls = new Array(urls.length);
var time = 1000;
for(var i=0;i<urls.length;i++){
(function(i){
setTimeout(function(){
htmls[i] = syncGet(urls[i]);
if(i == urls.length-1){
//continue here
}
},time*i);
})(i);
}
I had a similar problem where a big loop was blocking the whole browser in some older browsers, I solved it using :
function handlenext(idx,length) {
idx++
//do your stuff here base on idx.
if (idx < length) {
setTimeout(function(){handlenext(idx,length)},1)
} else {
initSuccessEnd()
}
}
var ln = something.length;
if (ln>0) {
handlenext(0,ln);
} else {
initSuccessEnd()
}
here initSuccessEnd is a callback function called when all is finished ..
After a research I think Mozilla's new iterator-generator stuff could be the most apropriate. (It's supported since FF2)
function doSomething()
{
//.....
var html=syncGet(url1);
yield true;
var html2=syncGet(url2);
yield true;
var html3=syncGet(url3);
yield true;
//......
yield false;
}
function iteratorRunner(iterator,timeout)
{
if (iterator.next())
{
setTimeout(function(){iteratorRunner(iterator,timeout)},timeout);
}
else
{
iterator.close();
}
}
var iterator=doSomething(); // returns an iterator immediately
iteratorRunner(iterator,1000); // runs the iterator and sleeps 1 second on every yield.
I hope greasemonkey will handle that...