I'm basically just trying to verify if a resource is reachable from the executing client. I can not use XHR, because the target resource doesn't allow that.
I'm pretty new to JS and am currently working with this ( executable here ):
var done = false;
var i = 1;
var t = "https://i.stack.imgur.com/Ya15i.jpg";
while(!done && i < 4)
{
console.log("try "+i);
done = chk(t);
sleep(1000);
i = i+1;
if (done)
{
console.log("Reachable!");
break;
}
else
{
console.log("Unreachable.");
}
}
function chk(target)
{
console.log("checking "+target)
fetch(target, {mode: 'no-cors'}).then(r=>{
return true;
})
.catch(e=>{
return false;
});
}
// busy fake sleep
function sleep(s)
{
var now = new Date().getTime();
while(new Date().getTime() < now + s){ /* busy sleep */ }
}
I was expecting this code to check for the resource, print the result, then wait for a sec. Repeat this until 3 tries were unsuccessful or one of them was successful.
Instead the execution blocks for a while, then prints all of the console.logs at once and the resource is never reachable (which it is).
I do know that the fetch operation is asynchronous, but I figured if I previously declare done and implement a sleep it should work. In the worst case, the while loop would use the previously declared done.
How do I achieve the described behavior? Any advice is welcome.
Your sleep function is blocking, what you really want is a recursive function that returns a promise after checking the url n times with a delay of y seconds etc.
Something like this
function chk(target, times, delay) {
return new Promise((res, rej) => { // return a promise
(function rec(i) { // recursive IIFE
fetch(target, {mode: 'no-cors'}).then((r) => { // fetch the resourse
res(r); // resolve promise if success
}).catch( err => {
if (times === 0) // if number of tries reached
return rej(err); // don't try again
setTimeout(() => rec(--times), delay ) // otherwise, wait and try
}); // again until no more tries
})(times);
});
}
To be used like this
var t = "https://i.stack.imgur.com/Ya15i.jpg";
chk(t, 3, 1000).then( image => {
console.log('success')
}).catch( err => {
console.log('error')
});
And note that this does not fail on 404 or 500, any response is a successful request.
The main problem is that you are trying to return from callback. That makes no sense.
But fetch is Promise based request you can use Promise to simulate delays as well
Something like this should do the trick
// promise based delay
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout))
// check if target can be fetched
const check = target => fetch(target, {...})
.then(response => response.ok)
const ping = (target, times = 3, timeout = 1000) => check(target)
.then(found => {
if(!found && times) { // still can check
// wait then ping one more time
return delay(timeout).then(() => ping(target, times - 1, timeout))
}
return found
})
ping('https://i.stack.imgur.com/Ya15i.jpg')
.then(found => {
console.log(found ? 'Reachable': 'Unreachable')
})
Your chk function returns undefined, you return true/false from promise callbacks not from container function.
You should use recursion and timeout in catch callback.
It will be something like this:
var i = 0;
var done = false;
var t = "https://i.stack.imgur.com/Ya15i.jpg";
(function chk(target){
console.log("checking "+target)
fetch(target, {mode: 'no-cors'}).then(r=>{
done = true;
console.log("Reachable!");
})
.catch(e=>{
console.log("Unreachable.");
if(i<4){
setTimeout(function(){
chk(target)
},1000)
}
});
})(t)
You can't return within a callback. When you do, it is the callback that is returning, not the parent function. If fact, the function chk is never returning anything.
What it sounds like you are intending to do is return the promise returned by fetch. And attempt to fetch three times.
Try this:
const numberOfTries =3;
currentTry = 1;
var t = "https://i.stack.imgur.com/Ya15i.jpg";
chk(t);
function tryCheck(resource, currentTry) {
chk(resource).done(function(){
console.log("Reachable!");
}).catch(function(e) {
console.log("Unreachable.");
if (currentTry >= numberOfTries) return;
sleep(1000);
tryCheck(resource, currentTry + 1);
});
}
function chk(resource) {
console.log("checking "+target);
return fetch(target, {mode: 'no-cors'});
}
Try this, Hope it works
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'image/jpeg');
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'no-cors',
cache: 'default' };
var myRequest = new Request('https://i.stack.imgur.com/Ya15i.jpg');
fetch(myRequest,myInit).then(function(response) {
...
});
Related
I'm trying to get a closure to return a value that is supposed to be updated once a promise is resolved (or rejected).
The following code works. Initially the internal variable from within the close returns NONE as expected.
Then the first Promise is launched, and once that is resolved, the internal variable is updated to FAIL.
The second Promise is a deliberate delay, just so that we can observe the change of the closured variable.
However, once the while loop is added to the equation, by uncommenting that loop(x) section, the update is not observable within the while loop.
I would expect to see this:
...
9963000000 NONE
9964000000 NONE
9965000000 NONE
9966000000 NONE
9967000000 NONE
9968000000 FAIL
9969000000 FAIL
9970000000 FAIL
9971000000 FAIL
9972000000 FAIL
9973000000 FAIL
9974000000 FAIL
...
I know it might be due to the single threaded blocking, but, is there a way to observe a dynamic external variable from within the while loop?
let sleep = async (ms) => new Promise ((resolve, reject) => setTimeout (resolve, ms));
let task = async (ms) => new Promise (function(resolve, reject) {
setTimeout (function(){
const error = true;
let result;
if(error){
result = '_NO_';
reject({'state': false, 'response': result});
}else{
result = '_YES_';
resolve({'state': true, 'response': result});
}
}, ms);
});
let loop = async (cb) => {
let i = 0;
while(i<10000000000){
const value = cb.getResponse();
(function() {
if(i%1000000==0){ console.log(i, value) };
i += 1;
})(i, value);
}
}
const linkResponse = (function(){
let response = 'NONE';
function setResponse(value) {response = value; return response};
function getResponse() {return response};
return { 'setResponse': setResponse, 'getResponse': getResponse };
});
const x = linkResponse();
console.log(x.getResponse());
(async () => {
task(3000)
.then(function(res){
console.log('__OK__', res);
let response = 'SUCCESS';
x.setResponse(response)
})
.catch(function(err){
console.log('error', err);
let response = 'FAIL';
x.setResponse(response)
});
sleep(6000)
.then(function(res){
console.log(x.getResponse())
});
//loop(x);
})();
Well, thanks for the help. Just as I was suspecting, it is indeed a blocked thread issue. I solved the problem with a recursive function. I just needed to have a long process running in the background and I naively thought that an infinite loop will do the job.
let loop2 = function(i, cb) {
if(i>100000){
return
}
console.log(i, cb.getResponse());
i += 1;
sleep(0)
.then(function(res){
loop2(i, cb);
});
}
And then calling:
loop2(0, x);
What is the correct way to implement a retry on error/condition without using any third party modules in nodejs, please?
I'm not sure how to call the same function on the error and how to then pass the original callback/data to the newly called function?
Do I need to destroy/end the sockets?
I've tried looking for examples but have only found reference to third party modules and http.get samples which don't seem to work. How does one test this?
I have attempted the below without success:
async pingApi(cb) {
let options = {
"method":"post",
"path": `/API/pingAPI?${this.auth}`, /ect do I reference this path?
}
};
let request = await http.request(options, (response) => {
let body = new Buffer(0);
response.on('data', (chunk) => {
body = Buffer.concat([body, chunk]);
});
response.on('end', function () {
if (this.complete) {
let decoded = new Buffer(body, 'base64').toString('utf8')
let json = JSON.parse(decoded);
if (json.result != 'OK') {
setTimeout(pingApi, 1000); //cant pass callback
} else {
cb(null, json.result) //works
}
}
});
})
request.end(); //does the socket need to be closed if retry is this function?
}
Any help, pointing in the right direction or criticism will be greatly appreciated as I think this is a very important learning curve for me.
Thank you in advance,
I'm not sure how to call the same function on the error and how to then pass the original callback/data to the newly called function?
I don't know for sure that everything else in your function is correct, but you can fix the recursion that you're asking about by changing this:
setTimeout(pingApi, 1000); //cant pass callback
to this:
setTimeout(() => {
this.pingApi(cb);
}, 1000);
You aren't showing the whole context here, but if pingApi() is a method, then you also need to keep track of the this value to you can call this.pingApi(db). You can preserve the value of this by using arrow function callbacks like this:
response.on('end', () => { ... });
Other things I notice that look off here:
There's no reason to use await http.request(). http.request() does not return a promise so using await with it does not do anything useful.
Without the await, there's then no reason for your function to be declared async since nobody is using a returned promise from it.
It's not clear what if (this.complete) is meant to do. Since this is inside a regular function callback, the value of this won't be your pingApi object. You should either save this higher in the scope typically with const self = this or all callbacks internally need to be arrow functions so the value of this is preserved.
You should probably put try/catch around JSON.parse() because it can throw if the input is not perfect JSON.
You should probably not retry forever. Servers really hate clients that retry forever because if something goes wrong, the client may just be bashing the server every second indefinitely. I'd suggest a certain number of max retries and then give up with an error.
Do I need to destroy/end the sockets?
No, that will happen automatically after the request ends.
How does one test this?
You have to create a test route in your server that returns the error condition for the first few requests and then returns a successful response and see if your code works with that.
Here's an attempt at a code fixup (untested):
const maxRetries = 10;
pingApi(cb, cnt = 0) {
let options = {
"method":"post",
"path": `/API/pingAPI?${this.auth}`, // ect do I reference this path?
};
let request = http.request(options, (response) => {
let body = new Buffer(0);
response.on('data', (chunk) => {
body = Buffer.concat([body, chunk]);
});
response.on('end', () => {
if (this.complete) {
let decoded = new Buffer(body, 'base64').toString('utf8')
try {
let json = JSON.parse(decoded);
if (json.result != 'OK') {
if (cnt < maxRetries)
setTimeout(() => {
this.pingApi(cb, ++cnt);
}, 1000);
} else {
cb(new Error("Exceeded maxRetries with error on pingApi()"));
}
} else {
cb(null, json.result) //works
}
} catch(e) {
// illegal JSON encountered
cb(e);
}
}
});
})
request.end();
}
Remaining open questions about this code:
What is this.complete doing and what this should it be referencing?
Why is there no request.write() to send the body of the POST request?
I know you ask for no external modules, but my preferred way of doing this would be to use promises and to use the request-promise wrapper around http.request() because it handles a lot of this code for you (checks response.status for you, parses JSON for you, uses promise interface, etc...). You can see how much cleaner the code is:
const rp = require('request-promise');
const maxRetries = 5;
pingApi(cnt = 0) {
let options = {
method: "post",
url: `http://somedomain.com/API/pingAPI?${this.auth}`,
json: true
};
return rp(options).then(result => {
if (result.result === "OK") {
return result;
} else {
throw "try again"; // advance to .catch handler
}
}).catch(err => {
if (cnt < maxRetries) {
return pingApi(++cnt);
} else {
throw new Error("pingApi failed after maxRetries")
}
});
}
And, then sample usage:
pingApi().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
your use of async/await with core node server intrigued me and I've tried to use much as possible of this new async features.
This is what I end up with: https://runkit.com/marzelin/pified-ping
const pify = require("util").promisify;
const http = require("http");
const hostname = "jsonplaceholder.typicode.com";
const failEndpoint = "/todos/2";
const goodEndpoint = "/todos/4";
let options = {
method: "get",
path: `${failEndpoint}`,
hostname
};
async function ping(tries = 0) {
return new Promise((res) => {
const req = http.request(options, async (response) => {
let body = new Buffer(0);
response.on("data", (chunk) => {
body = Buffer.concat([body, chunk]);
})
const on = pify(response.on.bind(response));
await on("end");
let decoded = new Buffer(body, 'base64').toString('utf8')
let json = JSON.parse(decoded);
if (json.completed) {
return res("all good");
}
if (tries < 3) {
console.log(`retrying ${tries + 1} time`);
return res(ping(tries + 1));
}
return res("failed");
})
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
// write data to request body
req.end();
})
}
const status = await ping();
"status: " + status
I'm tackling a project that requires me to use JavaScript with an API method call. I'm a Java programmer who has never done web development before so I'm having some trouble with it.
This API method is asynchronous and it's in a while loop. If it returns an empty array, the while loop finishes. Otherwise, it loops. Code:
var done = true;
do
{
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// Sets the boolean to true if the returned array is empty, or false otherwise.
done = (result.data().length === 0) ? true : false;
}
}
);
} while (!done);
This doesn't work. The loop ends before the value of "done" is updated. I've done some reading up on the subject and it appears I need to use promises or callbacks because the API call is asynchronous, but I can't understand how to apply them to the code I have above.
Help would be appreciated!
edit: see the bottom, there is the real answer.
I encourage you yo use the Promise API. Your problem can be solved using a Promise.all call:
let promises = [];
while(something){
promises.push(new Promise((r, j) => {
YourAsyncCall(() => r());
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(() => {
//All operations done
});
The syntax is in es6, here is the es5 equivalent (Promise API may be included externally):
var promises = [];
while(something){
promises.push(new Promise(function(r, j){
YourAsyncCall(function(){ r(); });
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(function(){
//All operations done
});
You can also make your api call return the promise and push it directly to the promise array.
If you don't want to edit the api_call_method you can always wrap your code in a new promise and call the method resolve when it finishes.
edit: I have seen now the point of your code, sorry. I've just realized that Promise.all will not solve the problem.
You shall put what you posted (excluding the while loop and the control value) inside a function, and depending on the condition calling it again.
Then, all can be wraped inside a promise in order to make the external code aware of this asynchronous execution. I'll post some sample code later with my PC.
So the good answer
You can use a promise to control the flow of your application and use recursion instead of the while loop:
function asyncOp(resolve, reject) {
//If you're using NodeJS you can use Es6 syntax:
async_api_call("method.name", {}, (result) => {
if(result.error()) {
console.error(result.error());
reject(result.error()); //You can reject the promise, this is optional.
} else {
//If your operation succeeds, resolve the promise and don't call again.
if (result.data().length === 0) {
asyncOp(resolve); //Try again
} else {
resolve(result); //Resolve the promise, pass the result.
}
}
});
}
new Promise((r, j) => {
asyncOp(r, j);
}).then((result) => {
//This will call if your algorithm succeeds!
});
/*
* Please note that "(...) => {}" equivals to "function(...){}"
*/
sigmasoldier's solution is correct, just wanted to share the ES6 version with async / await:
const asyncFunction = (t) => new Promise(resolve => setTimeout(resolve, t));
const getData = async (resolve, reject, count) => {
console.log('waiting');
await asyncFunction(3000);
console.log('finshed waiting');
count++;
if (count < 2) {
getData(resolve, reject, count);
} else {
return resolve();
}
}
const runScript = async () => {
await new Promise((r, j) => getData(r, j, 0));
console.log('finished');
};
runScript();
If you don't want to use recursion you can change your while loop into a for of loop and use a generator function for maintaining done state. Here's a simple example where the for of loop will wait for the async function until we've had 5 iterations and then done is flipped to true. You should be able to update this concept to set your done variable to true when your webservice calls have buffered all of your data rows.
let done = false;
let count = 0;
const whileGenerator = function* () {
while (!done) {
yield count;
}
};
const asyncFunction = async function(){
await new Promise(resolve => { setTimeout(resolve); });
};
const main = new Promise(async (resolve)=>{
for (let i of whileGenerator()){
console.log(i);
await asyncFunction();
count++;
if (count === 5){
done = true;
}
}
resolve();
});
main.then(()=>{
console.log('all done!');
});
Also you may try recursion solution.
function asyncCall(cb) {
// Some async operation
}
function responseHandler(result) {
if (result.error()) {
console.error(result.error());
} else if(result.data() && result.data().length) {
asyncCall(responseHandler);
}
}
asyncCall(responseHandler);
Here is a solution I came up with. Place this in an async function.
let finished = false;
const loop = async () => {
return new Promise(async (resolve, reject) => {
const inner = async () => {
if (!finished) {
//insert loop code here
if (xxx is done) { //insert this in your loop code after task is complete
finshed = true;
resolve();
} else {
return inner();
}
}
}
await inner();
})
}
await loop();
If you don't want to use Promises you can restructure your code like so:
var tasks = [];
var index = 0;
function processNextTask()
{
if(++index == tasks.length)
{
// no more tasks
return;
}
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// process data
setTimeout(processNextTask);
}
}
);
}
Your loop won't work, because it is sync, your async task is async, so the loop will finish before the async task can even respond. I'd reccomend you to use Promises to manage async tasks:
//first wrapping your API into a promise
var async_api_call_promise = function(methodName, someObject){
return new Promise((resolve, reject) => {
async_api_call(methodName, someObject, function(result){
if(result.error()){
reject( result.error() )
}else{
resolve( result.data() )
}
});
})
}
now to your polling code:
//a local utility because I don't want to repeat myself
var poll = () => async_api_call_promise("method.name", {/*Do stuff.*/});
//your pulling operation
poll().then(
data => data.length === 0 || poll(), //true || tryAgain
err => {
console.error(err);
return poll();
}
).then((done) => {
//done === true
//here you put the code that has to wait for your "loop" to finish
});
Why Promises? Because they do state-management of async operations. Why implement that yourself?
let taskPool = new Promise(function(resolve, reject) {
resolve("Success!");
});
let that = this;
while (index < this.totalPieces) {
end = start + thisPartSize;
if (end > filesize) {
end = filesize;
thisPartSize = filesize - start;
}
taskPool.then(() => {
that.worker(start, end, index, thisPartSize);
});
index++;
start = end;
}
Background
I am trying to create a factory function that executes a specific async function with a given delay.
For the purposes of this question, this will be the async function I refer to:
/*
* This is a simulation of an async function. Be imaginative!
*/
let asyncMock = function(url) {
return new Promise(fulfil => {
setTimeout(() => {
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
This function takes an url and it returns a JSON object containing that URL and some data.
All around my code, I have this function called in the following way:
asyncMock('http://www.bananas.pt')
.then(console.log);
asyncMock('http://www.berries.com')
.then(console.log);
//... badjillion more calls
asyncMock('http://www.oranges.es')
.then(console.log);
Problem
The problem here is that all these calls are made at exactly the same time, thus overloading the resources that asyncMoc is using.
Objective
To avoid the previous problem, I wish to delay the execution of all calls to asyncMoc by Xms.
Here is a graphic with what I pretend:
To achieve this I wrote the following approaches:
Using Promises
Using setInterval
Using Promises
let asyncMock = function(url) {
return new Promise(fulfil => {
setTimeout(() => {
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
let delayFactory = function(args) {
let {
delayMs
} = args;
let promise = Promise.resolve();
let delayAsync = function(url) {
return promise = promise.then(() => {
return new Promise(fulfil => {
setTimeout(() => {
console.log(`made request to ${url}`);
fulfil(asyncMock(url));
}, delayMs);
});
});
};
return Object.freeze({
delayAsync
});
};
/*
* All calls to any of its functions will have a separation of X ms, and will
* all be executed in the order they were called.
*/
let delayer = delayFactory({
delayMs: 500
});
console.log('running');
delayer.delayAsync('http://www.bananas.pt')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.fruits.es')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.veggies.com')
.then(console.log)
.catch(console.error);
This factory has a function called delayAsync that will delay all calls to asyncMock by 500ms.However, it also forces the nest execution of the call to wait for the result of the previous one - which in not intended.
The objective here is to make three calls to asyncMock within 500ms each, and 10s after receive three responses with a difference of 500ms.
Using setInterval
In this approach, my objective is to have a factory which has an array of parameters. Then, every 500ms, the timer will run an executor which will take a parameter from that array and return a result with it:
/*
* This is a simulation of an async function. Be imaginative!
*/
let asyncMock = function(url) {
return new Promise(fulfil => {
setTimeout(() => {
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
let delayFactory = function(args) {
let {
throttleMs
} = args;
let argsList = [];
let timer;
/*
* Every time this function is called, I add the url argument to a list of
* arguments. Then when the time comes, I take out the oldest argument and
* I run the mockGet function with it, effectively making a queue.
*/
let delayAsync = function(url) {
argsList.push(url);
return new Promise(fulfil => {
if (timer === undefined) {
console.log('created timer');
timer = setInterval(() => {
if (argsList.length === 0) {
clearInterval(timer);
timer = undefined;
} else {
let arg = argsList.shift();
console.log('making request ' + url);
fulfil(asyncMock(arg));
}
}, throttleMs);
} else {
//what if the timer is already running? I need to somehow
//connect it to this call!
}
});
};
return Object.freeze({
delayAsync
});
};
/*
* All calls to any of its functions will have a separation of X ms, and will
* all be executed in the order they were called.
*/
let delayer = delayFactory({
delayMs: 500
});
console.log('running');
delayer.delayAsync('http://www.bananas.pt')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.fruits.es')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.veggies.com')
.then(console.log)
.catch(console.error);
// a ton of other calls in random places in code
This code is even worse. It executes asyncMoch 3 times without any delay whatsoever, always with the same parameter, and then because I don't know how to complete my else branch, it does nothing.
Questions:
Which approach is better to achieve my objective and how can it be fixed?
I'm going to assume you want the promises returned by delayAsync to resolve based on the promises from asyncMock.
If so, I would use the promise-based approach and modify it like this (see comments):
// Seed our "last call at" value
let lastCall = Date.now();
let delayAsync = function(url) {
return new Promise(fulfil => {
// Delay by at least `delayMs`, but more if necessary from the last call
const now = Date.now();
const thisDelay = Math.max(delayMs, lastCall - now + 1 + delayMs);
lastCall = now + thisDelay;
setTimeout(() => {
// Fulfill our promise using the result of `asyncMock`'s promise
fulfil(asyncMock(url));
}, thisDelay);
});
};
That ensures that each call to asyncMock is at least delayMs after the previous one (give or take a millisecond thanks to timer vagaries), and ensures the first one is delayed by at least delayMs.
Live example with some debugging info:
let lastActualCall = 0; // Debugging only
let asyncMock = function(url) {
// Start debugging
// Let's show how long since we were last called
console.log(Date.now(), "asyncMock called", lastActualCall == 0 ? "(none)" : Date.now() - lastActualCall);
lastActualCall = Date.now();
// End debugging
return new Promise(fulfil => {
setTimeout(() => {
console.log(Date.now(), "asyncMock fulfulling");
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
let delayFactory = function(args) {
let {
delayMs
} = args;
// Seed our "last call at" value
let lastCall = Date.now();
let delayAsync = function(url) {
// Our new promise
return new Promise(fulfil => {
// Delay by at least `delayMs`, but more if necessary from the last call
const now = Date.now();
const thisDelay = Math.max(delayMs, lastCall - now + 1 + delayMs);
lastCall = now + thisDelay;
console.log(Date.now(), "scheduling w/delay =", thisDelay);
setTimeout(() => {
// Fulfill our promise using the result of `asyncMock`'s promise
fulfil(asyncMock(url));
}, thisDelay);
});
};
return Object.freeze({
delayAsync
});
};
/*
* All calls to any of its functions will have a separation of X ms, and will
* all be executed in the order they were called.
*/
let delayer = delayFactory({
delayMs: 500
});
console.log('running');
delayer.delayAsync('http://www.bananas.pt')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.fruits.es')
.then(console.log)
.catch(console.error);
// Let's hold off for 100ms to ensure we get the spacing right
setTimeout(() => {
delayer.delayAsync('http://www.veggies.com')
.then(console.log)
.catch(console.error);
}, 100);
.as-console-wrapper {
max-height: 100% !important;
}
Okay, so here's my solution to your problem. Sorry I had to rewrite your code to better be able to understand it. I hope you can interpret it anyway and get something out of it.
Calls 500ms between eachother using Promises (JSFiddle):
function asyncFunc(url) {
return new Promise(resolve => {
setTimeout(function() {
resolve({ url: url, data: 'banana' });
}, 2000);
});
}
function delayFactory(delayMs) {
var delayMs = delayMs;
var queuedCalls = [];
var executing = false;
this.queueCall = function(url) {
var promise = new Promise(function(resolve) {
queuedCalls.push({ url: url, resolve: resolve });
executeCalls();
});
return promise;
}
var executeCalls = function() {
if(!executing) {
executing = true;
function execute(call) {
asyncFunc(call.url).then(function(result) {
call.resolve(result);
});
setTimeout(function() {
queuedCalls.splice(queuedCalls.indexOf(call), 1);
if(queuedCalls.length > 0) {
execute(queuedCalls[0]);
} else {
executing = false;
}
}, delayMs)
}
if(queuedCalls.length > 0) {
execute(queuedCalls[0]);
}
}
}
}
var factory = new delayFactory(500);
factory.queueCall('http://test1').then(console.log); //2 sec log {url: "http://test1", data: "banana"}
factory.queueCall('http://test2').then(console.log); //2.5 sec log {url: "http://test2", data: "banana"}
factory.queueCall('http://test3').then(console.log); //3 sec log {url: "http://test3", data: "banana"}
factory.queueCall('http://test4').then(console.log); //3.5 sec log {url: "http://test4", data: "banana"}
Introduction
After reading both solutions, I have to say I am very thankful to both people who took their time to help me. It is moments like this (although rare) that make me proud of having a StackOverflow account.
This said, after reading both proposals, I came with one of my own, and I will explain which one I think is best and why.
My solution
My solution is based on #Arg0n's proposal, and it is a simplification/re-implementation of his code using the factory pattern in JavaScript and defended by Douglas Crockford, using ECMA6 features:
let asyncFunc = function(url) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve({
url: url,
data: 'banana'
});
}, 5000);
});
};
let delayFactory = function(args) {
let {
delayMs
} = args;
let queuedCalls = [];
let executing = false;
let queueCall = function(url) {
return new Promise((resolve, reject) => {
queuedCalls.push({
url,
resolve,
reject
});
if (executing === false) {
executing = true;
nextCall();
}
});
};
let execute = function(call) {
console.log(`sending request ${call.url}`);
asyncFunc(call.url)
.then(call.resolve)
.catch(call.reject);
setTimeout(nextCall, delayMs);
};
let nextCall = function() {
if (queuedCalls.length > 0)
execute(queuedCalls.shift());
else
executing = false;
};
return Object.freeze({
queueCall
});
};
let myFactory = delayFactory({
delayMs: 1000
});
myFactory.queueCall('http://test1')
.then(console.log)
.catch(console.log);
myFactory.queueCall('http://test2')
.then(console.log)
.catch(console.log);
myFactory.queueCall('http://test3')
.then(console.log)
.catch(console.log);
Why am I posting this extra solution? Because I think it is a vast improvement over Arg0n's proposal, for the following reasons:
No falsiness. Falsy values and expressions (like !executing) are a problem in JavaScript. I strongly recommend Appendix A: Awful parts of JavaScript.
Implements catch should the asyncMock fail
Use of Array.prototype.shift instead of Array.prototype.splice which is easier to read and improves performance.
No use of new keyword, no messing of the this reference
No inner functions. ESlint will thank you :P
Use of factories Douglas Crockford style
If you liked Arg0n's solution, I recommend you have a look at mine.
#Arg0n VS #T.J. Crowder ... FIGHT!
Which solution is better and why?
At first i was inclined to Arg0n's solution, because it took inspiration from one of my failed attempts and made it work. On itself, that is remarkable.
Furthermore, Timers in JavaScript have precision issues, and JavaScript also has issues when making computations with numbers (check 0.1 + 0.2 !== 0.3).
However, both solution use Timers. In fact, you need timers to achieve this behavior. Furthermore #T.J. Crowder's solution does not do arithmetic with floating points, but whole numbers, so his calculations are safe and sound.
One could point out that the Math library was a mistake in JavaScript imported from java, but honestly that is going to far and there is nothing wrong with it.
Furthermore, because T.J.'s solution does not have a data structure like Arg0n's solution has, its code is smaller as it encompasses less logic to maintain. There is no question from a technical point of view, his solution is the one to go for, in this specific case.
However, for those of you who don't master the math behind, Arg0n's avenue is a pretty solid one.
Conclusion
From a technical point of view, T.J.'s solution wins. However I can say that I enjoyed Arg0n's solution a lot, and specially my version of his post, which is the one I am likely to use.
I hope this post helps someone in the future !
I am trying to reuse the the data returned from promise here. But, the problem is, after the first call to checkPromise function, it immediately calls the second function, and the promise for the first function is not fulfilled, so it never returns any data, and hence it never enters the if clause. How do I reuse a promise?
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return new Promise(function (resolve, reject) {
request(url).spread(function(response, body) {
return resolve(body);
}).catch(function(err) {
console.error(err);
return reject(err);
});
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
}
checkPromise(url);
checkPromise(url);
You likely have a timing issue. Your apiCall() function is asynchronous. That means it finishes sometime later. As such, each time you call checkPromise(), all you're doing is starting a request and it finishes sometime later. So, you call it the first time and it starts a request (that has not finished yet). Then, your next call to checkPromise() gets called and it does it's if check before the first call has completed. Thus, it finds nothing in the cache yet.
Your code is running two requests in parallel, not one after the other.
If you actually want to wait until the first request is done before executing the second one, then you will have to actually structure your code to do that. You would need to make checkPromise() return a promise itself so code using it could known when it was actually done in order to execute something after it was done.
FYI, I don't see anything in your code that is actually related to reusing promises (which is something you cannot do because they are one-shot objects).
Here's one possible implementation:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
return Promise.resolve(rp);
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
}
}
checkPromise(url).then(function() {
checkPromise(url);
});
Significant changes:
Return the promise returned by request() rather than create yet another one.
Change checkPromise() so it always returns a promise whether the value is found in the cache or not so calling code can always work consistently.
Sequence the two checkPromise() calls so the first can finish before the second is executed.
A very different approach would be to actually wait on the cache if a result you are interested in is already being loaded. That could be done like this:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
// If it's a promise object in the cache, then loading
// If it's a value, then the value is already available
// Either way, we wrap it in a promise and return that
return Promise.resolve(obj[url]);
} else {
var p = apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
obj[url] = p;
return p;
}
}
checkPromise(url).then(function(result) {
// use result
});
checkPromise(url).then(function(result) {
// use result
});
few problems with your code, first in apiCall, you are doing a promise ant-pattern( no need for that new promise), second your checkPromise is doing a sync operation, so it must either return a promise or have a callback argument, so you code can be changed into:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
}).catch(function(err) {
console.error(err);
throw err;
});
}
function checkPromise(url) {
var promise = Promise.resolve();
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
return promise;
}
checkPromise(url).then(function(){
return checkPromise(url);
});
Given the way you are globally storing the result in 'obj[url]', it'd probably be easiest to do
function checkPromise(url) {
if (!obj[url]) obj[url] = apiCall(url);
obj[url].then(function(result) {
//do something
});
}
to basically make the request, if it hasn't already started, then attach a listener to the promise for when the result has loaded.
Here is the simplest example of how to prevent multiple API calls if there are multiple similar request for something (cache check for example)
var _cache = {
state: 0,
result: undefined,
getData: function(){
log('state: ' + this.state);
if(this.state === 0 ){ // not started
this.state = 1; // pending
this.promise = new Promise(function(resolve, reject) {
return (apiCall().then(data => { _cache.result = data; _cache.state = 2; resolve(_cache.result) }));
})
return this.promise;
}
else if(this.state === 1){ // pending
return this.promise;
}
else if(this.state === 2){// resolved
return Promise.resolve(this.result);
}
},
};
Simulating api call
function apiCall(){
return new Promise(function(resolve, reject) {
log('in promise')
setTimeout(() => {
log('promise resolving')
resolve(1);
}, 1000);
})
}
Making simultaneous requests.
_cache.getData().then(result => { log('first call outer: ' + result);
_cache.getData().then(result => { log('first call inner: ' + result); });
});
_cache.getData().then(result => { log('second call outer: ' + result);
_cache.getData().then(result => { log('second call inner: ' + result); });
});
Only one API call is maden. All others will wait for completion or use the resolved result if it already completed.