How do i iterate through a list and make sequential network calls using a sdk?
I am trying to use Coinbase's Node sdk and get the first 10 transactions for all accounts.
I have a list of accounts, and i iterating through them and calling client.account, client.transactions, and client.transactions(pagination). And im adding the results to an array of transactions and returning that array.
I couldn't get this to work with async/await or request-promises.
Any ideas?
https://developers.coinbase.com/api/v2#transactions
var rp = require('request-promise');
var coinbase = require('coinbase');
var client = new coinbase.Client({ 'apiKey': 'keyStuff', 'apiSecret': 'secretStuff' });
var accountList = ['acct1','acct2','acct3',];
var transactionList = [];
try {
let ps = [];
accountList.forEach(acctId => {
var account = client.getAccount(accountId, null);
ps.push(rp(account));
});
Promise.all(ps)
.then(responses => {
for (var i = 0; i < responses.length; i++) {
var result = responses[i];
rp(result.account.getTransactions(null))
.then(res => {
res.pagination = 10;
return rp(result.account.getTransactions(null, res.pagination));
}).catch(err => console.log(err))
.then(txns => {
try {
if (txns.length > 0) {
txns.forEach(function(txn) {
var transaction = {
"trade_type": "",
"price": ""
};
transaction.trade_type = txn.type;
transaction.price = txn.native_amount.amount;
transactionList.push(transaction);
});
}
}
catch (err) {
console.log(err);
}
});
}
}).catch(err => console.log(err));
return transactionList;
//-------------------------------------------------------------------
// if (accountList.length > 0) {
// for (let acctId of accountList) {
// console.log("account id: " + acctId);
// await delayTransactions(acctId);
// }
// console.log("got here last");
// return transactionList;
// }
}
catch (error) {
console.log(error);
}
The commented-out delay method has nested async calls like this:
await client.getAccount(accountId, async function(err, account) {
if (err) {
console.log(err);
}
else {
await account.getTransactions(null, async function(err, txns, pagination) {
.
.
.
Solved it by using async/await and promises. awaiting the coinbase methods wouldn't work because they aren't async functions (surprise!).
function delayedMethod() {
new Promise(resolve => {
client.getAccount(accountId, async function(err, account) {
if (err) {
console.log(err);
}
else {
account.getTransactions(null, async function(err, txns, pagination) {
.
.
.
resolve();
});
}
Related
I'm making a request to my database, I set the functions as asynchronous and to wait, but it still returns me undefined or Promise { pending }
how do I just return it when I have the result?
export const getGerente = async (req, res) => {
var query = "SELECT * FROM inventory;"
const r = await select(query)
console.log(r)
return res.json({message:"teste"})
}
export async function select(query) {
var teste = await client.connect(() =>{
client
.query(query)
.then((resultado) => {
console.log('sucess!!');
return resultado.rows
/*
const rows=resultado.rows
rows.map(x =>{
console.log(x.name)
})*/
})
.catch((erro) => {
console.log("erro: " + erro.message);
})
.then((teste) => {
console.log('Finished execution, exiting now');
process.exit();
});
})
}
result: Promise { pending }
I'm calling her for a request
Your select function is not awaiting the client.connect properly.
Try this for select function -
export async function select(query) {
const promisifiedRows = new Promise((resolve, reject) => {
client.connect((err) => {
if (err) {
reject(err); // err in connecting
} else {
console.log('Connected!');
client.query(query, (err, rows) => {
if (err) {
reject(err); // err while exceuting the query
} else {
resolve(rows);
}
});
}
});
});
const rows = await promisifiedRows();
return rows;
}
While trying to get the rows from resultSet using toQueryStream() I can only find the rows in console. But not able to return value using promises as toQueryStream() function uses eventListener to resolve rows. My code is given below please suggest to get the row values.
function getPosCounter() {
return oracledb.getConnection(kcdConnStr)
.then(function (conn) {
return conn.execute(`BEGIN GETPOSCOUNTER(:CV_1); END;`, { // EXECUTE ORACLE PROCEDURE
CV_1: { dir: oracledb.BIND_OUT, type: oracledb.CURSOR } //CURSOR DEFINED FOR OUT PARAM
})
.then((result) => {
var resRows = new Array();
var resultSet = result.outBinds.CV_1; //RESULT SET FOR OUTPUT
var queryStream = resultSet.toQueryStream(); //QUERYSTREAM INITIALIZED FOR CURSOR VALUES
var consumeStream = new Promise((resolve, reject) => {
queryStream.on('data', function (row) {
console.log(row);
});
queryStream.on('error', reject);
queryStream.on('close', resolve);
})
.then(rows => {
console.dir(rows); //RETURN ROW VALUES
});
})
.catch((err) => {
conn.close();
console.error(err);
return 'failure';
})
});
}
Before this issue I faced, I was not aware about the nodeJs-Stream feature which is quiet difficult to understand for me. So I found a documentation on Oracle Community about getting the rows from result set. Make it possible in the below way.
function getPosCounter() {
return oracledb.getConnection(kcdConnStr)
.then(function (conn) {
return conn.execute(`BEGIN USP_POSCOUNTER(:CV_1); END;`, { // EXECUTE ORACLE PROCEDURE
CV_1: { dir: oracledb.BIND_OUT, type: oracledb.CURSOR } //CURSOR DEFINED FOR OUT PARAM
})
.then((result) => {
var resRows = [];
var resultSet = result.outBinds.CV_1; //RESULT SET FOR OUTPUT
var queryStream = resultSet.toQueryStream(); //QUERYSTREAM INITIALIZED FOR CURSOR VALUES
return consumeStream = new Promise((resolve, reject) => {
queryStream.on('data', (row) => {
resRows.push(row); //STORE ROWS IN TO BLANK ARRAY
});
queryStream.on('error', reject);
queryStream.on('close', () => {
resolve(resRows); //RETURN ON RESOLVING ALL THE ROWS
conn.close();
});
});
})
.catch((err) => {
conn.close();
//console.error(err);
return 'failure';
})
});
}
Instead of fiddling with promises and streams, you could code like this:
const oracledb = require('oracledb');
const dbConfig = require('./dbconfig.js');
if (process.platform === 'darwin') {
oracledb.initOracleClient({libDir: process.env.HOME + '/Downloads/instantclient_19_8'});
}
async function run() {
let connection;
try {
connection = await oracledb.getConnection(dbConfig);
await connection.execute(
`create or replace procedure usp_poscounter(p out sys_refcursor) as
begin
open p for select postal_code from locations order by location_id;
end;`);
const r = await getPosCounter();
console.dir(r, { depth: null });
} catch (err) {
console.error(err);
} finally {
if (connection) {
try {
await connection.close();
} catch (err) {
console.error(err);
}
}
}
}
async function getPosCounter() {
let conn;
try {
conn = await oracledb.getConnection(dbConfig); // should really use a pool instead
const result = await conn.execute(
`BEGIN USP_POSCOUNTER(:CV_1); END;`,
{ CV_1: { dir: oracledb.BIND_OUT, type: oracledb.CURSOR } }
);
let row, outRows = [];
while ((row = await result.outBinds.CV_1.getRow())) {
outRows.push(row);
}
// With node-oracledb 5.2 you will be able to simplify this loop to be the
// following one line. Note with any node-oracledb version, if the number
// of rows is large, then you will want your architecture to deal with
// batches of rows insteading of returning one big array.
//
// const outRows = await result.outBinds.CV_1.getRows(); // empty arg gets all rows
return (outRows);
} catch (err) {
console.error(err);
} finally {
if (conn) {
try {
await conn.close();
} catch (err) {
console.error(err);
}
}
}
}
run();
I am finding all users wallets in nested foreach loop but i am unable to figure out where to use resolve() for return callback,
function genwallets() {
return new Promise((resolve, reject) => {
var i = 0;
try {
db.users.findAll({}).then((users)=>{
db.supportedTokens.findAll({}).then((tokens)=>{
users.forEach(function(user) {
tokens.forEach(function(token) {
db.wallets.findOne({where:{userId: user['id'], supportedTokenId: token['id']}}).then((wallet)=>{
console.log(JSON.stringify(wallet));
})
});
});
})
});
} catch (err) {
console.log(err);
}
});
}
forEach doesn't work with promises. either use for...of or Promise.all something like this
function genwallets() {
return new Promise((resolve, reject) => {
var i = 0;
try {
db.users.findAll({}).then(users => {
db.supportedTokens.findAll({}).then(tokens => {
for(const user of users) {
for(const token of tokens) {
db.wallets
.findOne({
where: { userId: user["id"], supportedTokenId: token["id"] }
})
.then(wallet => {
console.log(JSON.stringify(wallet));
});
}
}
});
});
} catch (err) {
console.log(err);
}
});
}
by the way you dont need to wrap it in promise.
You could simplify this using async/await
async function genwallets() {
const users = await db.users.findAll({});
const tokens = await db.supportedTokens.findAll({});
for(const user of users) {
for(const token of tokens) {
const wallet = await db.wallets
.findOne({
where: { userId: user["id"], supportedTokenId: token["id"] }
});
}
}
}
So I have a method, which I want to call multiple times in a loop. This is the function:
function PageSpeedCall(callback) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${websites[0]}&strategy=mobile&key=${keys.pageSpeed}`;
// second call
var results = '';
https.get(pagespeedCall, resource => {
resource.setEncoding('utf8');
resource.on('data', data => {
results += data;
});
resource.on('end', () => {
callback(null, results);
});
resource.on('error', err => {
callback(err);
});
});
// callback(null, );
}
As you see this is an async function that calls the PageSpeed API. It then gets the response thanks to the callback and renders it in the view. Now how do I get this to be work in a for/while loop? For example
function PageSpeedCall(websites, i, callback) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${websites[i]}&strategy=mobile&key=${keys.pageSpeed}`;
// second call
var results = '';
https.get(pagespeedCall, resource => {
resource.setEncoding('utf8');
resource.on('data', data => {
results += data;
});
resource.on('end', () => {
callback(null, results);
});
resource.on('error', err => {
callback(err);
});
});
// callback(null, );
}
var websites = ['google.com','facebook.com','stackoverflow.com'];
for (let i = 0; i < websites.length; i++) {
PageSpeedCall(websites, i);
}
I want to get a raport for each of these sites. The length of the array will change depending on what the user does.
I am using async.parallel to call the functions like this:
let freeReportCalls = [PageSpeedCall, MozCall, AlexaCall];
async.parallel(freeReportCalls, (err, results) => {
if (err) {
console.log(err);
} else {
res.render('reports/report', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}
});
I tried to use promise chaining, but for some reason I cannot put it together in my head. This is my attempt.
return Promise.all([PageSpeedCall,MozCall,AlexaCall]).then(([ps,mz,al]) => {
if (awaiting != null)
var areAwaiting = true;
res.render('admin/', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}).catch(e => {
console.error(e)
});
I tried doing this:
return Promise.all([for(let i = 0;i < websites.length;i++){PageSpeedCall(websites, i)}, MozCall, AlexaCall]).
then(([ps, mz, al]) => {
if (awaiting != null)
var areAwaiting = true;
res.render('admin/', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}).catch(e => {
console.error(e)
});
But node just said it's stupid.
And this would work if I didn't want to pass the websites and the iterator into the functions. Any idea how to solve this?
To recap. So far the functions work for single websites. I'd like them to work for an array of websites.
I'm basically not sure how to call them, and how to return the responses.
It's much easier if you use fetch and async/await
const fetch = require('node-fetch');
async function PageSpeedCall(website) {
const pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${website}&strategy=mobile&key=${keys.pageSpeed}`;
const result = await fetch(pagespeeddCall);
return await result.json();
}
async function callAllSites (websites) {
const results = [];
for (const website of websites) {
results.push(await PageSpeedCall(website));
}
return results;
}
callAllSites(['google.com','facebook.com','stackoverflow.com'])
.then(results => console.log(results))
.error(error => console.error(error));
Which is better with a Promise.all
async function callAllSites (websites) {
return await Promise.all(websites.map(website => PageSpeedCall(website));
}
Starting on Node 7.5.0 you can use native async/await:
async function PageSpeedCall(website) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${website}&strategy=mobile&key=${keys.pageSpeed}`;
return await promisify(pagespeedCall);
}
async function getResults(){
const websites = ['google.com','facebook.com','stackoverflow.com'];
return websites.map(website => {
try {
return await PageSpeedCall(website);
}
catch (ex) {
// handle exception
}
})
}
Node http "callback" to promise function:
function promisify(url) {
// return new pending promise
return new Promise((resolve, reject) => {
// select http or https module, depending on reqested url
const lib = url.startsWith('https') ? require('https') : require('http');
const request = lib.get(url, (response) => {
// handle http errors
if (response.statusCode < 200 || response.statusCode > 299) {
reject(new Error('Failed to load page, status code: ' + response.statusCode));
}
// temporary data holder
const body = [];
// on every content chunk, push it to the data array
response.on('data', (chunk) => body.push(chunk));
// we are done, resolve promise with those joined chunks
response.on('end', () => resolve(body.join('')));
});
// handle connection errors of the request
request.on('error', (err) => reject(err))
})
}
Make PageSpeedCall a promise and push that promise to an array as many times as you need, e.g. myArray.push(PageSpeedCall(foo)) then myArray.push(PageSpeedCall(foo2)) and so on. Then you Promise.all the array.
If subsequent asynch calls require the result of a prior asynch call, that is what .then is for.
Promise.all()
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
I have a function that runs periodically, that updates the item.price of some Documents in my Prices Collection. The Price Collection has 100k+ items. The function looks like this:
//Just a helper function for multiple GET requests with request.
let _request = (urls, cb) => {
let results = {}, i = urls.length, c = 0;
handler = (err, response, body) => {
let url = response.request.uri.href;
results[url] = { err, response, body };
if (++c === urls.length) {
cb(results);
}
};
while (i--) {
request(urls[i], handler);
}
};
// function to update the prices in our Prices collection.
const update = (cb) => {
Price.remove({}, (err, remove) => {
if (err) {
return logger.error(`Error removing items...`);
}
logger.info(`Removed all items... Beginning to update.`);
_request(urls, (responses) => {
let url, response, gameid;
for (url in responses) {
id = url.split('/')[5].split('?')[0];
response = responses[url];
if (response.err) {
logger.error(`Error in request to ${url}: ${err}`);
return;
}
if (response.body) {
logger.info(`Request to ${url} successful.`)
let jsonResult = {};
try {
jsonResult = JSON.parse(response.body);
} catch (e) {
logger.error(`Could not parse.`);
}
logger.info(`Response body for ${id} is ${Object.keys(jsonResult).length}.`);
let allItemsArray = Object.keys(jsonResult).map((key, index) => {
return {
itemid: id,
hash_name: key,
price: jsonResult[key]
}
});
Price.insertMany(allItemsArray).then(docs => {
logger.info(`Saved docs for ${id}`)
}, (e) => {
logger.error(`Error saving docs.`);
});
}
}
if (cb && typeof cb == 'function') {
cb();
}
})
});
}
As you can see, to avoid iterating through 100k+ Documents, and updating each and every one of them separately, I delete them all at the beginning, and just call the API that gives me these Items with prices, and use InsertMany to Insert all of them into my Prices Collection.
This updating process will happen every 30 minutes.
But I just now realised, what if some user wants to check the Prices and my Prices Collection is currently empty because it's in the middle of updating itself?
The Question
So do I have to iterate through all of them in order to not delete it? (Remember, there are MANY documents to be updated every 30 mins.) Or is there another solution?
Here's a picture of how my Prices Collection looks (there are 100k docs like these, I just want to update the price property):
Update:
I have re-written my update function a bit and now it looks like this:
const update = (cb = null) => {
Price.remove({}, (err, remove) => {
if (err) {
return logger.error(`Error removing items...`);
}
logger.info(`Removed all items... Beginning to update.`);
_request(urls, (responses) => {
let url, response, gameid;
for (url in responses) {
gameid = url.split('/')[5].split('?')[0];
response = responses[url];
if (response.err) {
logger.error(`Error in request to ${url}: ${err}`);
return;
}
if (response.body) {
logger.info(`Request to ${url} successful.`)
let jsonResult = {};
try {
jsonResult = JSON.parse(response.body);
} catch (e) {
logger.error(`Could not parse.`);
}
logger.info(`Response body for ${gameid} is ${Object.keys(jsonResult).length}.`);
let allItemsArray = Object.keys(jsonResult).map((key, index) => {
return {
game_id: gameid,
market_hash_name: key,
price: jsonResult[key]
}
});
let bulk = Price.collection.initializeUnorderedBulkOp();
allItemsArray.forEach(item => {
bulk.find({market_hash_name: item.market_hash_name})
.upsert().updateOne(item);
});
bulk.execute((err, bulkers) => {
if (err) {
return logger.error(`Error bulking: ${e}`);
}
logger.info(`Updated Items for ${gameid}`)
});
// Price.insertMany(allItemsArray).then(docs => {
// logger.info(`Saved docs for ${gameid}`)
// }, (e) => {
// logger.error(`Error saving docs.`);
// });
}
}
if (cb && typeof cb == 'function') {
cb();
}
})
});
}
Notice the bulk variable now (Thanks #Rahul) but now, the collection takes ages to update. My processor is burning up and it literally takes 3+ minutes to update 60k+ documents. I honestly feel like the previous method, while it might delete all of them and then reinserting them, it also takes 10x faster.
Anyone?
From my experience (updating millions of mongo docs on a hourly basis), here's a realistic approach to very large bulk updates:
do all your API calls separately and write results in as bson into a file
invoke mongoimport and import that bson file into a new empty collection prices_new. Javascript, let alone high-level OO wrappers, are just too slow for that
rename prices_new -> prices dropTarget=true (this will be atomic hence no downtime)
Schematically, it would look like this in JS
let fname = '/tmp/data.bson';
let apiUrls = [...];
async function doRequest(url) {
// perform a request and return an array of records
}
let responses = await Promise.all(apiUrls.map(doRequest));
// if the data too big to fit in memory, use streams instead of this:
let data = flatMap(responses, BSON.serialize).join('\n'));
await fs.writeFile(fname, data);
await child_process.exec(`mongoimport --collection prices_new --drop ${fname}`);
await db.prices_new.renameCollection('prices', true);
There's no need to clear the database and do a fresh insert. You can use the bulkWrite() method for this or use the updateMany() method to do the updates.
You can refactor the existing code to
const update = (cb) => {
_request(urls, responses => {
let bulkUpdateOps = [], gameid;
responses.forEach(url => {
let response = responses[url];
gameid = url.split('/')[5].split('?')[0];
if (response.err) {
logger.error(`Error in request to ${url}: ${response.err}`);
return;
}
if (response.body) {
logger.info(`Request to ${url} successful.`)
let jsonResult = {};
try {
jsonResult = JSON.parse(response.body);
} catch (e) {
logger.error(`Could not parse.`);
}
Object.keys(jsonResult).forEach(key => {
bulkUpdateOps.push({
"updateOne": {
"filter": { market_hash_name: key },
"update": { "$set": {
game_id: gameid,
price: jsonResult[key]
} },
"upsert": true
}
});
});
}
if (bulkUpdateOps.length === 1000) {
Price.bulkWrite(bulkUpdateOps).then(result => {
logger.info(`Updated Items`)
}).catch(e => logger.error(`Error bulking: ${e}`));
bulkUpdateOps = [];
}
});
if (bulkUpdateOps.length > 0) {
Price.bulkWrite(bulkUpdateOps).then(result => {
logger.info(`Updated Items`)
}).catch(e => logger.error(`Error bulking: ${e}`));
}
});
if (cb && typeof cb == 'function') {
cb();
}
}
I have not tested anything but you can try this, might be helpful. I am using bluebird library for concurrency.
let _request = (url) => {
return new Promise((resolve, reject) => {
request(url, (err, response, body) => {
if (err) {
reject(err);
}
resolve(body);
});
});
};
const formatRespose = async (response) => {
// do stuff
return {
query: {}, // itemid: id,
body: {}
};
}
const bulkUpsert = (allItemsArray) => {
let bulk = Price.collection.initializeUnorderedBulkOp();
return new Promise((resolve, reject) => {
allItemsArray.forEach(item => {
bulk.find(item.query).upsert().updateOne(item.body);
});
bulk.execute((err, bulkers) => {
if (err) {
return reject(err);
}
return resolve(bulkers);
});
});
}
const getAndUpdateData = async (urls) => {
const allItemsArray = urls.map((url) => {
const requestData = await _request(url); // you can make this also parallel
const formattedData = formatRespose(requestData); // return {query: {},body: {} };
return formattedData;
});
return await (bulkUpsert(allItemsArray));
};
function update() {
// split urls into as per your need 100/1000
var i, j, chunkUrls = [],
chunk = 100;
for (i = 0, j = urls.length; i < j; i += chunk) {
chunkUrls.push(getAndUpdateData(urls.slice(i, i + chunk)));
}
Bluebird.map(chunkUrls, function (chunk) {
return await chunk;
}, {
concurrency: 1 // depends on concurrent request change 1 = 100 request get and insert in db at time
}).then(function () {
console.log("done");
}).catch(function () {
console.log("error");
});
}