Node js async convert to sync - javascript

I need to push data to array synchronously. First API request get image key base one that need to get image data within loop.
var deasync = require('deasync');
router.get('/a', function(req, res) {
var username="user";
var passw ="pass";
var op = [];
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
//this is first api request
client.get(global.apiUrl+"V1/ProductItem", args,
function (data, response) {
//this is second api request
data.forEach(function(img) {client.get(global.apiUrl+"V1/ImagePreview/"+img.AvatarKey,args,
function (data2, response){
img['data']=data2.Image;
deasync(pushData(img));
});
});
});
function pushData(img){
op.push(img);//array push
}
res.render('test1', { "out":JSON.stringify(op) });
});

As much as I think deasync is a poor choice of solving your particular issue, the key to using it, is to "deasync" asynchronous functions. As Array.push is synchronous, deasync'ing Array.push makes no sense
having read the documentation for deasync, it's fairly simple to use
var deasync = require('deasync');
// create a sync client.get
function syncClientGet(client, url, args) {
var inflight = true;
var ret;
client.get(url, args, function(data, response) {
// as your original code ignores response, ignore it here as well
ret = data;
inflight = false;
});
deasync.loopWhile(() => inflight);
return ret;
}
router.get('/a', function(req, res) {
var username = "user";
var passw = "pass";
var op = [];
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
let data = syncClientGet(client, global.apiUrl + "V1/ProductItem", args);
data.forEach(function(img) {
let data2 = syncClientGet(client, global.apiUrl + "V1/ImagePreview/" + img.AvatarKey, args);
img['data'] = data2.Image;
op.push(img);
});
res.render('test1', {
"out": JSON.stringify(op)
});
});
However, embracing asynchronicity, the code you posted could be easily written as
router.get('/a', function (req, res) {
var username = "user";
var passw = "pass";
var op = [];
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
client.get(global.apiUrl + "V1/ProductItem", args, function (data, response) {
data.forEach(function (img) {
client.get(global.apiUrl + "V1/ImagePreview/" + img.AvatarKey, args, function (data2, response) {
img['data'] = data2.Image;
op.push(img);
if (img.length == data.length) {
res.render('test1', {
"out": JSON.stringify(op)
});
}
});
});
});
});
or, using Promises
router.get('/a', function (req, res) {
var username = "user";
var passw = "pass";
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
// create a Promisified client get
var clientGetPromise = function clientGetPromise(client, url, args) {
return new Promise(function (resolve, reject) {
return client.get(url, args, function (data, response) {
return resolve(data);
});
});
};
clientGetPromise(client, global.apiUrl + "V1/ProductItem", args).then(function (data) {
return Promise.all(data.map(function (img) {
return clientGetPromise(client, global.apiUrl + "V1/ImagePreview/" + img.AvatarKey, args).then(function (data2) {
img['data'] = data2.Image;
return img;
});
}));
}).then(function (op) { // op is an Array of img because that's how Promise.all rolls
return res.render('test1', { "out": JSON.stringify(op) });
});
});

Related

javascript async await not waiting

Here are the functions I have.
async function check_saved_url() {
$.get("https://ipinfo.io/json", function (response) {
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
$.get(url, function(data, status) {
console.log("Data: " + data + "\nStatus: " + status);
if (data.includes('0 results')) {
return 'unknown';
} else {
return data;
}
});
}, "jsonp");
}
const func1 = async() => {
return await check_saved_url();
}
const func2 = async() => {
let domain_preference = '';
domain_preference = await func1();
console.log("domain_preference: ",domain_preference);
}
func2();
This method is from this answer.
As you can see, there are two jquery ajax to get data from server.
The problem is, the func1 and func2 never waits until check_saved_url returns value.
This is what I see in console.
The top red line is the output of func2, which must wait until check_saved_url runs, whose result is the following 2 circles.
I am not sure why this persists to happen and hours of copying answers from everywhere didn't help me.
You probably need to develop your understanding of promises and how async JavaScript works a little.
If you have to use jQuery, you could promisify the jQuery $.ajax method (which appears to use a very old version of the promise API?), and then use that with modern JS constructs like async/await.
const pGet = (url, dataType = 'jsonp') =>
new Promise((success, error) => $.ajax({url, dataType, success, error}))
const IP_URL = 'https://ipinfo.io/json'
const PREFERENCE_URL_PREFIX = 'https://www.example.com/getDomain.php?ip='
const go = async () => {
try {
const { ip } = await pGet(IP_URL)
const preference = await pGet(`${PREFERENCE_URL_PREFIX}${ip}`)
//do something with the preference
} catch(err) {
// handle errors
}
}
go()
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
If you don't have to use jQuery, I'd avoid it and use the fetch API provided by modern host environments.
const IP_URL = 'https://ipinfo.io/json'
const PREFERENCE_URL_PREFIX = 'https://www.example.com/getDomain.php?ip='
const go = async () => {
try {
const { ip } = await (await fetch(IP_URL)).json()
const preference = await fetch(`${PREFERENCE_URL_PREFIX}${ip}`)
//do something with the preference
} catch(err) {
// handle errors
}
}
go()
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
You need to return a Promise from check_saved_url. Inside of the Promise, you then need to use resolve to replace return. You can also use reject(new Error("error")) if there was an error.
async function check_saved_url() {
return new Promise((resolve, reject) => {
$.get("https://ipinfo.io/json", function (response) {
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
$.get(url, function(data, status) {
console.log("Data: " + data + "\nStatus: " + status);
if (data.includes('0 results')) {
// Instead of return, use resolve()
//return 'unknown';
resolve('unknown');
// You can also pass an error with `reject(new Error("error"))`
} else {
// Instead of return, use resolve()
//return data;
resolve(data);
}
});
}, "jsonp");
});
}
const func1 = async() => {
return await check_saved_url();
}
const func2 = async() => {
let domain_preference = '';
domain_preference = await func1();
console.log("domain_preference: ",domain_preference);
}
func2();
You can read more about promises on MDN
There is no promise returned by check_saved_url nor is $.get returning a promise. You need to wrap it all in a promise
async function check_saved_url() {
return new Promise((resolve, reject) => {
$.get("https://ipinfo.io/json", function (response) {
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
$.get(url, function(data, status) {
console.log("Data: " + data + "\nStatus: " + status);
if (data.includes('0 results')) {
resolve('unknown');
} else {
resolve(data);
}
})
.fail(reject);
}, "jsonp")
.fail(reject);
})
}
Clean the code
To have a cleaner and reusable code you can wrap $.get in a Promise
async function async_get(url) {
return new Promise((resolve, reject) => {
const jqxhr = $.get(url);
jqxhr.done(() => resolve({ json: jqxhr.responseJSON, status: jqxhr.status }));
jqxhr.fail(() => reject(jqxhr));
})
}
async function check_saved_url() {
const { json: response } = await async_get("https://ipinfo.io/json");
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
const { json: data, status } = await $.get(url);
console.log("Data: " + data + "\nStatus: " + status);
return data.includes('0 results') ? 'unknown' : data;
}
Your function check_saved_url does not return a promise - this is why await is not working for it.
The solution would be to return a promise in check_saved_url.
Suppose I have a function create_promise which I want to await (this mimics your 'get' request):
function create_promise() {
return new Promise(resolve => {
setTimeout(() => resolve("Done!"), 500);
});
}
const func1 = async() => {
const result = await create_promise();
return result + " <- result";
};
func1().then(console.log);
I will suggest you to use fetch API check_saved_url(). It return promise. You can read more about it here. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Code will go like this :
async function check_saved_url() {
let connect = await fetch("https://ipinfo.io/json")
let response = await connect.json()
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
// $.get(url, function(data, status) {
// console.log("Data: " + data + "\nStatus: " + status);
// if (data.includes('0 results')) {
// return 'unknown';
// } else {
// return data;
// }
// });
// }, "jsonp");
}

Issues with JSON.parse on input object form S3 - Node.js

Node newbie here I have a problem that I have been banging my head against for a few days now and I'm sure I am missing something small but my google too has failed me so far.
I have an AWS Firehose that is writing CloudWatch logs to S3 that are then picked up by a lambda function parsed and ingested into Elasticsearch. Or at least that is the plan. I reason I am using Firehose is that the data is coming from remote AWS accounts and sending all the logs to one place s3. I am able to pull the logs once in S3. Cloud watch logs by default are gzipped so the firehose is simply writing the files to S3 no transforming or anything. Now the issue :). Once I read the file from S3 I attempt to send it to a function transform to parse the data but it fails when trying to loop through the log which is JSON. I did a console.log(payload) and it appears to be in JSON format but running it through JSON.stringify the back to parsing does nothing. JSON.parse will work but the JSON will not be valid.
I am attaching the code below. I am not the original author someone in my company found it on GitHub. I just added the S3 parts to it.
I added a file that was one of the logs as an example it's in a public s3 bucket if anyone wants to grab it. No worries its all test data nothing special.
https://s3.amazonaws.com/node-issue-stackoverflow/cwl-test-11-2018-08-26-00-45-34-84a4c3de-179a-4bf2-9376-895cdc063e6b+(1)
Pastebin Link
// v1.1.2
var https = require('https');
var zlib = require('zlib');
var crypto = require('crypto');
var AWS = require('aws-sdk');
var endpoint = process.env.es_endpoint;
var s3 = new AWS.S3();
var params;
exports.handler = function(input, context) {
// Get the event from S3 based on input
params = {Bucket: input.Records[0].s3.bucket.name, Key: input.Records[0].s3.object.key};
console.log(input.Records[0].s3);
s3.getObject(params, function(error, event){
if (error) { context.fail(error); return; }
console.log(event);
console.log(event.Body);
// decode input from base64
var zippedInput = new Buffer(event.Body, 'base64');
// decompress the input
zlib.gunzip(zippedInput, function(error, buffer) {
if (error) { context.fail(error); return; }
// console.log(buffer.toString());
// parse the input from JSON
var awslogsData = buffer.toString('utf8');
// transform the input to Elasticsearch documents
var elasticsearchBulkData = transform(awslogsData);
// skip control messages
if (!elasticsearchBulkData) {
console.log('Received a control message');
context.succeed('Control message handled successfully');
return;
}
// post documents to the Amazon Elasticsearch Service
post(elasticsearchBulkData, function(error, success, statusCode, failedItems) {
console.log('Response: ' + JSON.stringify({
"statusCode": statusCode
}));
if (error) {
console.log('Error: ' + JSON.stringify(error, null, 2));
if (failedItems && failedItems.length > 0) {
console.log("Failed Items: " +
JSON.stringify(failedItems, null, 2));
}
context.fail(JSON.stringify(error));
} else {
console.log('Success: ' + JSON.stringify(success));
context.succeed('Success');
}
});
});
});
};
function transform(payload) {
if (payload.messageType === 'CONTROL_MESSAGE') {
return null;
}
var bulkRequestBody = '';
payload.logEvents.forEach(function(logEvent) {
var timestamp = new Date(1 * logEvent.timestamp);
// index name format: cwl-YYYY.MM.DD
var indexName = [
'cwl-' + timestamp.getUTCFullYear(), // year
('0' + (timestamp.getUTCMonth() + 1)).slice(-2), // month
('0' + timestamp.getUTCDate()).slice(-2) // day
].join('.');
var source = buildSource(logEvent.message, logEvent.extractedFields);
source['#id'] = logEvent.id;
source['#timestamp'] = new Date(1 * logEvent.timestamp).toISOString();
source['#message'] = logEvent.message;
source['#owner'] = payload.owner;
source['#log_group'] = payload.logGroup;
source['#log_stream'] = payload.logStream;
var action = { "index": {} };
action.index._index = indexName;
action.index._type = payload.logGroup;
action.index._id = logEvent.id;
bulkRequestBody += [
JSON.stringify(action),
JSON.stringify(source),
].join('\n') + '\n';
});
return bulkRequestBody;
}
function buildSource(message, extractedFields) {
if (extractedFields) {
var source = {};
for (var key in extractedFields) {
if (extractedFields.hasOwnProperty(key) && extractedFields[key]) {
var value = extractedFields[key];
if (isNumeric(value)) {
source[key] = 1 * value;
continue;
}
jsonSubString = extractJson(value);
if (jsonSubString !== null) {
source['$' + key] = JSON.parse(jsonSubString);
}
source[key] = value;
}
}
return source;
}
jsonSubString = extractJson(message);
if (jsonSubString !== null) {
return JSON.parse(jsonSubString);
}
return {};
}
function extractJson(message) {
var jsonStart = message.indexOf('{');
if (jsonStart < 0) return null;
var jsonSubString = message.substring(jsonStart);
return isValidJson(jsonSubString) ? jsonSubString : null;
}
function isValidJson(message) {
try {
JSON.parse(message);
} catch (e) { return false; }
return true;
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function post(body, callback) {
var requestParams = buildRequest(endpoint, body);
var request = https.request(requestParams, function(response) {
var responseBody = '';
response.on('data', function(chunk) {
responseBody += chunk;
});
response.on('end', function() {
var info = JSON.parse(responseBody);
var failedItems;
var success;
if (response.statusCode >= 200 && response.statusCode < 299) {
failedItems = info.items.filter(function(x) {
return x.index.status >= 300;
});
success = {
"attemptedItems": info.items.length,
"successfulItems": info.items.length - failedItems.length,
"failedItems": failedItems.length
};
}
var error = response.statusCode !== 200 || info.errors === true ? {
"statusCode": response.statusCode,
"responseBody": responseBody
} : null;
callback(error, success, response.statusCode, failedItems);
});
}).on('error', function(e) {
callback(e);
});
request.end(requestParams.body);
}
function buildRequest(endpoint, body) {
var endpointParts = endpoint.match(/^([^\.]+)\.?([^\.]*)\.?([^\.]*)\.amazonaws\.com$/);
var region = endpointParts[2];
var service = endpointParts[3];
var datetime = (new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, '');
var date = datetime.substr(0, 8);
var kDate = hmac('AWS4' + process.env.AWS_SECRET_ACCESS_KEY, date);
var kRegion = hmac(kDate, region);
var kService = hmac(kRegion, service);
var kSigning = hmac(kService, 'aws4_request');
var request = {
host: endpoint,
method: 'POST',
path: '/_bulk',
body: body,
headers: {
'Content-Type': 'application/json',
'Host': endpoint,
'Content-Length': Buffer.byteLength(body),
'X-Amz-Security-Token': process.env.AWS_SESSION_TOKEN,
'X-Amz-Date': datetime
}
};
var canonicalHeaders = Object.keys(request.headers)
.sort(function(a, b) { return a.toLowerCase() < b.toLowerCase() ? -1 : 1; })
.map(function(k) { return k.toLowerCase() + ':' + request.headers[k]; })
.join('\n');
var signedHeaders = Object.keys(request.headers)
.map(function(k) { return k.toLowerCase(); })
.sort()
.join(';');
var canonicalString = [
request.method,
request.path, '',
canonicalHeaders, '',
signedHeaders,
hash(request.body, 'hex'),
].join('\n');
var credentialString = [ date, region, service, 'aws4_request' ].join('/');
var stringToSign = [
'AWS4-HMAC-SHA256',
datetime,
credentialString,
hash(canonicalString, 'hex')
] .join('\n');
request.headers.Authorization = [
'AWS4-HMAC-SHA256 Credential=' + process.env.AWS_ACCESS_KEY_ID + '/' + credentialString,
'SignedHeaders=' + signedHeaders,
'Signature=' + hmac(kSigning, stringToSign, 'hex')
].join(', ');
return request;
}
function hmac(key, str, encoding) {
return crypto.createHmac('sha256', key).update(str, 'utf8').digest(encoding);
}
function hash(str, encoding) {
return crypto.createHash('sha256').update(str, 'utf8').digest(encoding);
}

How to improve this code in Node.js and Express.js avoiding callback hell

I have a method in one of my controller. The purpose of the controller, is print an array of urls using webshot package.
This is the code in question:
router.post('/capture', function (req, res, next) {
//Check params remove
var json = JSON.parse(req.body.data);
var promise = new Promise(function (resolve, reject) {
var totalImages = Object.keys(json).length;
var arrayListUrlImages = new Array(totalImages);
var counter = 0;
var completeDir = dir + ''; //Directory URL
for (var value of json) {
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
var anotherValue = value.anotherValue;
(function (anotherValue) {
webshot(url, folder, options, function (err) {
// screenshot now saved
if (err === null) {
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
arrayListUrlImages.push(urlImage);
counter++;
console.log("Counter: " + counter);
if (counter === totalImages) {
resolve(arrayListUrlImages);
}
}
else {
reject(err);
}
});
})(anotherValue);
}
}).then(function (arrayImages) {
res.send(arrayImages);
}).catch(function (errorVale) {
res.send(null);
});
});
This code is working without problems... but I would like to do better. I don't know how many URLs need to check (this is important detail because I need to do a for each or similar).
I have read about async package... Is better option move this code to something like async.parallel? Can I use yield in my code?
Thanks!
Since you are using Promise, I recommend Promise.all.
It returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
Seems like it solves your problem.
Example:
downloadOne = url => new Promise(resolve => {
webshot(url, ....., (err, res) => resolve(res));
})
router.post('/capture', function (req, res, next) {
var urls = JSON.parse(req.body.data);
Promise.all(urls.map(downloadOne)).then(req.send);
}
This is an example of code flow based on inner functions:
router.post('/capture', function (req, res, next) {
// Definitions
// Load image
function loadImage(value) {
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
return webshotPromise(url, folder, options);
}
// Load whebshot as a promise
function webshotPromise(url, folder, options) {
return new Promise((resolve, reject) => {
webshot(url, folder, options, function (err) {
if (err) {
reject(err);
}
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
resolve(urlImage);
}
});
}
// The method flow
const json = JSON.parse(req.body.data);
// Get json keys and iterate over it to load
Promise.all(
Object.getOwnPropertyNames(json).map(key => loadImage(json[key]))
)
// Got list of urls
.then((list) => {
res.json(list);
}, (error) => {
console.error(error);
res.json(null);
});
});
You don't need to use async for such simple example. Use native promises:
router.post('/capture', function (req, res, next) {
//Check params remove
const json = JSON.parse(req.body.data);
Promise.all(Object.getOwnPropertyNames(json).map((key) => {
var value = json[key];
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
return new Promise((resolve, reject) => {
webshot(url, folder, options, function (err) {
if (err) {
reject(err);
return;
}
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
resolve(urlImage);
}
});
}))
.then((listOfUrls) => {
res.json(listOfUrls); // List of URLs
}, (error) => {
console.error(error);
res.json(null);
});
});
Honestly, your code looks fine.
If you are not going to add more logic here, leave it as it is.
What can be done better, is migrating it to ES6 syntax and extracting anotherValue function but I am not aware if this applicable for your case.

async callback for loop response out of order

I do a async call in a for loop and i know that the response is coming async but how can i get my response always in the same order. Here's my code:
setInterval(function () {
callback = function (response)
{
var temp2 = '';
var str = "";
test = [];
console.log('STATUS: ' + response.statusCode);
response.setEncoding('utf8');
response.on('data', function (chunk)
{
str += chunk;
});
response.on('end', function ()
{
console.log("end found");
temp2 = JSON.parse(str);
for (var i in temp2['build'])
{
test.push(temp2['build'][i]['id']);
var req3 = http.request({
host: host, // here only the domain name
auth: auth,
port: 8111,
path: '/httpAuth/app/rest/builds/id:' + test[i] + '/statistics/', // the rest of the url with parameters if needed
method: 'GET', // do GET
headers: { "Accept": "application/json" }
}, callback2);
req3.end();
}
});
}
var req4 = http.request(options4, callback);
req4.end();
callback2 = function (response) {
//console.log('STATUS: ' + response.statusCode);
//console.log('HEADERS: ' + JSON.stringify(response.headers));
response.setEncoding('utf8');
var str2 = "";
response.on('data', function (chunk) {
str2 += chunk;
});
response.on('end', function () {
points.push(parseInt(JSON.parse(str2)["property"][2]["value"]));
});
j++;
if (j == test.length) {
var sumTotal = 0;
var sumThree = 0;
var status = '';
for (var i in points) {
sumTotal += points[i];
}
var averageTotal = parseInt(Math.round(sumTotal / points.length));
for (var i = 0; i < 3; i++) {
sumThree += points[i];
}
var averageThree = parseInt(Math.round(sumThree / 3));
/*if(averageThree>averageTotal)
{
status='warning';
}
else
{
status='ok';
}*/
console.log('average: ' + averageThree + ' average 100 ' + averageTotal + ' status ' + status);
//send_event('speed', {current: averageThree/*, status: status*/, last: averageTotal});
j = 0;
points = [];
}
}
}, 15 * 1000);
so my question is how can i be sure my response 'point's' have always the same order. I've tried sending the var i to the callback function but can't get it to work
edit:
changed formatting.
The output of the first callback:
{
"count":100,
"nextHref":"/httpAuth/app/rest/builds/?locator=buildType:bt2,count:100,status:SUCCESS,start:100",
"build":[
{
"id":17469,
"number":"5075",
"status":"SUCCESS",
"buildTypeId":"bt2",
"startDate":"20140224T183152+0100",
"href":"/httpAuth/app/rest/builds/id:17469",
"webUrl":"http://x.x.x.x:8111/viewLog.html?buildId=17469&buildTypeId=bt2"
},
{
"id":17464,
"number":"5074",
"status":"SUCCESS",
"buildTypeId":"bt2",
"startDate":"20140224T165758+0100",
"href":"/httpAuth/app/rest/builds/id:17464",
"webUrl":"http://x.x.x.x:8111/viewLog.html?buildId=17464&buildTypeId=bt2"
},
{
"id":17461,
"number":"5073",
"status":"SUCCESS",
"buildTypeId":"bt2",
"startDate":"20140224T161852+0100",
"href":"/httpAuth/app/rest/builds/id:17461",
"webUrl":"http://x.x.x.x:8111/viewLog.html?buildId=17461&buildTypeId=bt2"
},
This output contains 100 items. From this output I take the id number and make a new request with this array of id's. This new callback gives me the build duration but the problem is because this happens asynchronously the response I get is not from the latest build but from the first response. So my question is how can i get these build speed array in the right order
It's not recommended to use anonymous function in a for loop.
The best way (for me), it's to use the async library.
A simple exemple to answer your question :
var objectList = [
{"name":"Doe", "firstname":"John", "position":1},
{"name":"Foo", "firstname":"Bar", "position":2},
{"name":"Gates", "firstname":"Bill", "position":3},
{"name":"Jobs", "firstname":"Steve", "position":4},
];
var arr = [];
async.each(objectList, function(person, callback) {
arr.push(person); // async.each is asynchronous, so at the end, the order will be bad
}, function(err) {
async.sortBy(arr, function(p, callback) { // Reorder
callback(err, p.position);
}, function(err, results) {
callback(null, results); // Send the result
});
});
This example will work for your issue.

Issue with Async Operation Setting Object Property in JS

I'm developing a client-side library for a web-based service and I'm having some issues with setting an object variable and later retrieving it.
Here is the start of the library
var QuickBase = function(username, password, apptoken, realm) {
this.username = username;
this.password = password;
this.apptoken = (typeof apptoken === "undefined") ? '' : apptoken;
this.realm = (typeof realm === "undefined") ? 'www' : realm;
this.ticket = '';
this.dbid = '';
this.payload = '<qdbapi>';
this.init = function() {
var self = this;
this.authenticate(this.username, this.password, null, null, function(data) {
var errcode = $(data).find('errcode').text();
if(errcode > 0)
throw new Error($(data).find('errtext').text());
self.ticket = $(data).find('ticket').text();
});
}
this.setBaseUrl = function() {
var endpoint = this.dbid == '' ? 'main' : this.dbid;
this.baseUrl = 'https://' + this.realm + '.quickbase.com/db/' + endpoint;
}
this.transmit = function(method, callback) {
this.setBaseUrl();
if(this.apptoken)
this.payload += '<apptoken>' + this.apptoken + '</apptoken>';
if(this.ticket)
this.payload += '<ticket>' + this.ticket + '</ticket>';
this.payload += '</qdbapi>';
console.log(this.payload);
$.ajax({
url: this.baseUrl,
type: 'POST',
data: this.payload,
dataType: 'xml',
headers: {
'Content-Type': 'application/xml',
'QUICKBASE-ACTION': method
},
success: callback
});
this.payload = '<qdbapi>';
}
this.addSettingsToPayload = function(settings) {
for(var key in settings) {
this.payload += '<' + key + '>' + settings[key] + '</' + key + '>';
}
}
this.authenticate = function(username, password, hours, udata, callback) {
this.payload += '<username>' + username + '</username>';
this.payload += '<password>' + password + '</password>';
this.payload += (typeof hours === "undefined") ? '' : '<hours>' + hours + '</hours>';
this.payload += (typeof udata === "undefined") ? '' : '<udata>' + udata + '</udata>';
this.transmit('API_Authenticate', callback);
}
And here's the use case:
var username = 'foo',
password = 'bar',
token = 'footoken',
realm = 'foorealm';
window.qb = new QuickBase(username, password, token, realm);
$.when(qb.init()).then(function(){
console.log(qb); // shows the object with ticket set
console.log(qb.ticket); // empty
qb.doQuery(); // breaks because internal this.ticket is empty
});
So my question is why is qb.ticket not being set and not available in future function calls?
In addition, is there a way that I don't have to wrap .init() in .when?
Basically, init sets the ticket that all future API methods will need. If I just call qb.init() and then qb.doQuery(), there is no guarantee init() will have finished - but if I use .when, won't that mean all future method calls would need to be inside of the .then callback? That seems ugly.
$.when expects a promise object. If it doesn't get one it will execute the callback immediately. You have to return the promise from the $.ajax call, or create your own.
But since you are only working with one promise you don't even need when. You could do:
qb.init().done(qb.doQuery.bind(qb));
Example:
var QuickBase = function(username, password, apptoken, realm) {
// ...
this.init = function() {
var self = this;
return this.authenticate(this.username, this.password, null, null).done(function(data) {
var errcode = $(data).find('errcode').text();
if(errcode > 0)
throw new Error($(data).find('errtext').text());
self.ticket = $(data).find('ticket').text();
});
}
this.transmit = function(method) {
// ...
var promise = $.ajax({
url: this.baseUrl,
type: 'POST',
data: this.payload,
dataType: 'xml',
headers: {
'Content-Type': 'application/xml',
'QUICKBASE-ACTION': method
}
});
this.payload = '<qdbapi>';
return promise;
}
this.authenticate = function(username, password, hours, udata) {
// ..
return this.transmit('API_Authenticate');
}
}
As alternative, you can just make .init accept a callback:
this.init = function(callback) {
var self = this;
this.authenticate(this.username, this.password, null, null, function(data) {
var errcode = $(data).find('errcode').text();
if(errcode > 0)
throw new Error($(data).find('errtext').text());
self.ticket = $(data).find('ticket').text();
callback();
});
};
and then:
qb.init(function(){
qb.doQuery();
});
Easiest approach is probably to return a promise from init(), as follows :
this.init = function() {
var self = this;
var dfrd = $.Deferred();
this.authenticate(this.username, this.password, null, null, function(data) {
var errcode = $(data).find('errcode').text();
if(errcode > 0)
throw new Error($(data).find('errtext').text());
self.ticket = $(data).find('ticket').text();
dfrd.resolve();
});
return dfrd.promise();
}
Then :
window.qb = new QuickBase(username, password, token, realm);
qb.init().then(function() {
console.log(qb);
console.log(qb.ticket);
qb.doQuery();
});

Categories