This snippet of code gets executed until the end of the getRefreshToken function is at its last line. The play_song function does not get triggered.
Am I misunderstanding the way JS works? For now I have included the call to play_song at the end of the getRefreshToken function, and of course that works as expected. However, I am very confused why it doesn't just execute function after function in the if-statement.
function checkTime () {
var now = new Date();
if (now.getHours() == time[0] && now.getMinutes() == time[1]) {
getRefreshToken();
play_song();
}
}
The contents of getRefreshToken:
function getRefreshToken() {
console.log('Starting token request')
// requesting access token from refresh token
var refresh_token = tokens.refresh;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
//play_song(); //only a temporary fix
})
}
Yes in JavaScript, functions are not necessarily synchronous as you might be familiar with in other programming languages. JavaScript is single threaded, however operations such as fetching data, querying API's, databases, I/O etc. all happen in parallel to the main execution of your code.
I assume getRefreshToken() is making some sort of network request and the fact you mentioned play_song works when put at the end of getRefreshToken() gives it away.
There are many ways to deal with this in JavaScript, here's a good resource.
Here's an example for your situation, I removed some unneeded code for the purpose of the example:
// Using callbacks
function checkTime() {
getRefreshToken(play_song);
}
function getRefreshToken(callback) {
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
callback();
})
}
// Using Promises
function checkTime() {
getRefreshToken()
.then(() => {
play_song();
})
}
function getRefreshToken(callback) {
return new Promise((resolve, reject) => {
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
resolve();
})
})
}
// Using Async/Await
async function checkTime() {
await getRefreshToken()
play_song();
}
function getRefreshToken(callback) {
return new Promise((resolve, reject) => {
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
resolve();
})
})
}
Related
I would like to retry my request in a promise. I would like launch my refresh if I have always an 401 error as a loop : (if I have 401 loop on refresh until 200)
I tried with this :
const request = require('request');
let conf = require('../conf');
let core_service = require('coreService');
let self = module.exports = {
get_count_questions: function() {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri: 'http://api/count-questions',
auth: {
'bearer': conf.token
},
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
resolve(body);
} else if (!error && response.statusCode === 401) {
core_service.refreshToken().then((data) => {
console.log('token refresh');
return self.get_count_questions();
})
} else {
reject(error);
}
})
});
}
};
I tried with just 'self.get_count_questions();' without return, but it's not work. I have not error message, just my app freeze.
I see in my console.log "token refresh", but after my app freeze...
Edit
I modified with this, It's like better but the refresh token it's very slow. Just before 401, my app stop, and after about 1 minutes 40 seconds, run:
else if (!error && response.statusCode === 401) {
console.log('need refresh token');
core_service.refreshToken()
.then((response) => {
console.log(response);
resolve(self.get_count_questions())
} );
}
My refreshToken function :
refreshToken: function () {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri : 'http://api/refresh',
auth : {
'bearer': conf.token
},
json : true
}, function (error, response, body) {
console.log('=====> refresh token <======');
conf.token = body.data;
console.log('new Token');
console.log('=====> end refresh token <======');
if (!error && response.statusCode === 200) {
resolve('Refresh token successful');
} else {
reject('Error refresh');
}
})
});
}
If I refresh my token on each request, I have a problem :
if (!error && response.statusCode === 200) {
core_service.refreshToken().then((data)=> {
resolve(body);
});
}
You have to resolve the returned promise. When you resolve using a promise, you basically say, complete this promise with the result of that promise.
var prom = function() {
return new Promise((resolve, reject) => {
console.log('request start')
setTimeout(() => {
console.log('request finish')
let ran = Math.random();
if (ran < 0.1)
resolve('success');
else if (ran >= 0.1 && ran < 0.98)
setTimeout(() => {
console.log('retry');
resolve(prom());
}, 500);
else
reject('error');
}, 500);
});
};
prom().then(console.log.bind(console), console.log.bind(console));
So you should update your else if block like this:
else if (!error && response.statusCode === 401) {
console.log('need refresh token');
core_service.refreshToken()
.then(() => resolve(self.get_count_questions()));
}
You're making a recursive call, but you're never actually doing anything with its promise. Therefore, your original promise never resolves.
You need to pass the promise from the recursive call (to refreshToken().then()) to resolve().
Now you almost have it.
However:
return core_service.refreshToken()
.then(self.get_count_questions);
You're returning that to the request() callback; that return value is not used.
Instead, you need to resolve your original promise to the new promise from then(), by passing it to your original resolve() function:
resolve(core_service.refreshToken().then(...));
I know that is not optimal solution but it might helps
const request = require('request');
let conf = require('../conf');
let core_service = require('coreService');
let self = module.exports = {
get_count_questions: function() {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri: 'http://api/count-questions',
auth: {
'bearer': conf.token
},
json: true
}, function (error, response, body) {
try{
if (!error && response.statusCode === 200) {
resolve(body);
} else if (!error && response.statusCode === 401) {
throw new Error(response.statusCode);
} else {
reject(error);
}
}catch(exc){if(exc === 401){
core_service.refreshToken().then((data) => {
console.log('token refresh');
return self.get_count_questions();
})
}
}
})
});
}
};
You need to call the initial resolve/reject functions after you retried the request:
let self = module.exports = {
get_count_questions: function() {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri: 'http://api/count-questions',
auth: {
'bearer': conf.token
},
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
resolve(body);
} else if (!error && response.statusCode === 401) {
core_service.refreshToken().then((data) => {
console.log('token refresh');
self.get_count_questions().then((data) => {
// call initial resolve function
resolve(data);
}).catch((error) => {
// call initial reject function
reject(error);
});
}).catch((error) => {
// reject if refreshToken fails
reject(error);
});
} else {
reject(error);
}
})
});
}
};
You also have to make sure, that the second call actually resolves/rejects and doesn't land in another 401. Because else you have an infinite recursion.
Is there a better/more beautiful way to call multiple APIs after another (in serial) as in my example?
var request = require('request');
request('http://www.test.com/api1', function (error, response, body) {
if (!error && response.statusCode == 200) {
request('http://www.test.com/api1', function (error, response, body) {
if (!error && response.statusCode == 200) {
request('http://www.test.com/api1', function (error, response, body) {
if (!error && response.statusCode == 200) {
//And so on...
}
})
}
})
}
})
Depending on which version of node you are using, promises should be native...
https://nodejs.org/en/blog/release/v4.0.0/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
var request = require('request');
getRequest('http://www.test.com/api1').then(function (body1) {
// do something with body1
return getRequest('http://www.test.com/api2');
}).then(function (body2) {
// do something with body2
return getRequest('http://www.test.com/api3');
}).then(function (body3) {
// do something with body3
//And so on...
});
function getRequest(url) {
return new Promise(function (success, failure) {
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
success(body);
} else {
failure(error);
}
});
});
}
Use async.series
If you want to do same operation on a set of URLs, use async.map
Promise can also be used as others suggested.
If you are new with asynchronous programming, I suggest to start with async module then move to Promise (or coroutines, async/await) once you have clearer understanding.
Example:
var request = require('request');
async.series([
function(callback) {
request('http://www.test.com/api1', function(error, response, body) {
if (!error && response.statusCode == 200) {
return callback(null, response);
}
return callback(error || new Error('Response non-200'));
}
},
function(callback) {
request('http://www.test.com/api2', function(error, response, body) {
if (!error && response.statusCode == 200) {
return callback(null, response);
}
return callback(error || new Error('Response non-200'));
}
}
],
// optional callback
function(err, results) {
if (err) {
// Handle or return error
}
// results is now array of responses
});
You can use request-promise instead of request and then you would be able to chain all the promises!
https://github.com/request/request-promise
var rp = require('request-promise');
rp(options)
.then(function (body) {
return rp(...)
}).then(()){
...
}
In my honest opinion and if the order is not important you should do all the requests in parallel!
Use Promises.
https://www.promisejs.org
https://www.promisejs.org/patterns/
It is much better than callbacks and it can execute one request after another in serial.
You may want to consider using JavaScript Promises. They sure provide a more beautiful way to handle callbacks.
How can I chain the multiple promise? For instance:
var promise = new Promise(function(resolve, reject) {
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
// Resolve the result.
resolve(true);
} else {
reject(Error(false));
}
});
});
promise.then(function(result) {
// Stop here if it is false.
if (result !== false) {
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body); // <-- I want to pass the result to the next promise.
} else {
reject(Error(false));
}
});
}
}, function(err) {
// handle error
});
promise.then(function(result) {
// Stop here if it is false.
if (result !== false) {
// handle success.
console.log(result);
}
}, function(err) {
// handle error.
});
Error:
resolve(body);
ReferenceError: resolve is not defined
Any ideas?
When chaining Promises, then return value from a function given to then should either be a Promise, or the value to pass along.
In your case, since you're making an async call, you'll just return another promise and call reject or resolve within there. If it wasn't async, you could just return the value, or throw an error, which also gets passed along to the next then or error handler/catch as appropriate.
Also, you need to chain them together, because each then() returns a different Promise.
So, something like this:
var promise = new Promise(function(resolve, reject) {
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
// Resolve the result.
resolve(true);
} else {
reject(Error(false));
}
});
});
promise.then(function(result) {
// Stop here if it is false.
if (result !== false) {
var airportCode = result;
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
return new Promise(function (resolve, reject) {
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body);
} else {
reject(Error(false));
}
});
});
}
}).then(function(result) {
// Stop here if it is false.
if (result !== false) {
// handle success.
console.log(result);
}
}).catch(function (err) {
// handle error
});
Here is a JSFiddle with a working version: JSFiddle
I currently have working code that does a request and checks if it receives a successful status code of 200. I would like to grow on this and loop it where it will keep sending requests until the status code is 200. I tried using a while loop but was not receiving the correct results. Thanks for the help!
request('http://0.0.0.0:9200', function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log('success');
do(something);
}
else {
console.log('fail');
}
});
Would be something like:
let retry = (function() {
let count = 0;
return function(max, timeout, next) {
request('http://0.0.0.0:9200', function (error, response, body) {
if (error || response.statusCode !== 200) {
console.log('fail');
if (count++ < max) {
return setTimeout(function() {
retry(max, timeout, next);
}, timeout);
} else {
return next(new Error('max retries reached'));
}
}
console.log('success');
next(null, body);
});
}
})();
retry(20, 1000, function(err, body) {
do(something);
});
You can set a max number of retries and a timeout between retries. So that you do not introduce an infinite loop, and you do not deliver the final punch to an overloaded request target ^^
I wanted a little more intuitive answer including promises. I build on top of miggs answer within a try/catch the code below with promises and axios.
Based on a simple example of recursive functions
const throwNumbers = (count = 0) => {
console.log(count);
if (count++ < 10) {
throwNumbers(count);
} else {
console.log('max reached');
};
};
You can put anything else on the try part and handle error codes in the catch part. You have to set a max number of retries, which is 10 in my case.
let getResponse = async(count = 0) => {
try {
const axiosResponse = await axios.get(someURL, {
params: {
parameter1: parameter1,
},
});
return axiosResponse;
} catch (error) {
if (error || error.status != 200) {
console.error('failed, retry');
if (count++ < 10) {
return getResponse(count);
} else {
throw new Error('max retries reached');
};
} else {
throw error;
};
};
};
You would call the function with the following and handle the body or whatever with the response value.
let response = await getResponse();
console.log('This is the response:', response);
Has no timeout but works for me.
An API call returns the next 'page' of results. How do I recurse over that result callback elegantly?
Here is an example of where I need to do this:
var url = 'https://graph.facebook.com/me/?fields=posts&since=' + moment(postFromDate).format('YYYY-MM-DD') + '&access_token=' + User.accessToken;
request.get({
url: url,
json: true
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
_.each(body.posts.data, function (post) {
User.posts.push(post); //push some result
});
if (body.pagination.next) { // if set, this is the next URL to query
//?????????
}
} else {
console.log(error);
throw error;
}
});
I would suggest wrapping the call in a function and just keep calling it until necessary.
I would also add a callback to know when the process has finished.
function getFacebookData(url, callback) {
request.get({
url: url,
json: true
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
_.each(body.posts.data, function (post) {
User.posts.push(post); //push some result
});
if (body.pagination.next) { // if set, this is the next URL to query
getFacebookData(body.pagination.next, callback);
} else {
callback(); //Call when we are finished
}
} else {
console.log(error);
throw error;
}
});
}
var url = 'https://graph.facebook.com/me/?fields=posts&since=' +
moment(postFromDate).format('YYYY-MM-DD') + '&access_token=' + User.accessToken;
getFacebookData(url, function () {
console.log('We are done');
});