Is it possible to bind this in bluebird map? - node.js

So I've tried to write up an example as best I could of what I'm trying to do, this isn't a very practical example but I tried to simplify it, but I feel like I may have complicated this in trying to make an example.
class add {
constructor(baseValue) {
this.base = baseValue;
return new Promise((resolve, reject) => {
resolve(this);
});
}
addBase(num) {
return new Promise((resolve, reject) => {
resolve(this.base + num);
});
}
}
const values = [1,2,3,4,5];
Promise.try(() => {
return new add(5);
}).then((add) => {
// we want to find if a 5 exists in the results
const addPromise = Promise.resolve(values).map(add.addBase, {concurrency: 1});
return Promise.try(() => {
return addPromise;
}).then((results) => {
for(let i = 0; i < results.length; i++) {
if(results[i] === 10) {
return i;
}
}
// doesn't exist
return null;
});
}).then((result) => {
if(result === null) {
console.log('10 does not exist');
} else {
console.log('10 is at position ' + result);
}
})
<script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.min.js"></script>
If you run this you'll get an error that you can't get base of undefined, this is because of the mapping in bluebird. const addPromise = Promise.resolve(values).map(add.addBase, {concurrency: 1}); Is there a way on this line to bind the add object to this when making these calls?

This is actually a little simpler than you're making it I think. You are passing the raw function into map(), but you should probably be passing an arrow function instead. Consider this simple class and code that tries to use map() by passing add():
class Test{
constructor(n) {
this.n = n
}
add(k) {
return this.n + k
}
}
let t = new Test(10)
let arr = [1, 2, 3]
// error TypeError: undefined is not an object (evaluating 'this.n')
arr.map(t.add)
This throws an error because map isn't calling add() from the object, it just thinks it's a function. An easy fix is to call map like this:
class Test {
constructor(n) {
this.n = n
}
add(k) {
return this.n + k
}
}
let t = new Test(10)
let arr = [1, 2, 3]
let mapped = arr.map((n) => t.add(n))
console.log(mapped)
You could also use:
let mapped = arr.map(t.add.bind(t))
but to me that's harder to read and understand quickly. I'm not sure what's going on with all the immediately resolved promises in your code, but changing the way you call map() makes that error go away. (there's another error later where you reference i that's not in scope.)

You've pretty much answered your own question…
class add {
constructor(baseValue) {
this.base = baseValue;
return new Promise((resolve, reject) => {
resolve(this);
});
}
addBase(num) {
return new Promise((resolve, reject) => {
resolve(this.base + num);
});
}
}
const values = [1,2,3,4,5];
Promise.try(() => {
return new add(5);
}).then((add) => {
// we want to find if a 5 exists in the results
const addPromise = Promise.resolve(values).map(add.addBase.bind(add), {concurrency: 1});
return Promise.try(() => {
return addPromise;
}).then((results) => {
for(let i = 0; i < results.length; i++) {
if(results[i] === 10) {
return i;
}
}
// doesn't exist
return null;
});
}).then((result) => {
if(result === null) {
console.log('10 does not exist');
} else {
console.log('10 is at position ' + result);
}
})
<script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.min.js"></script>

Related

foreach loop in sync function in nodejs

I have code written
function getDetails (req, res) {
const dbQuery = `call spGetSLAReportsDetails('${req.body.domainId}', ${req.body.days},'${req.body.type}','${req.body.app}')`
try {
connectDatabase(dbQuery).then((rows) => {
if (!_.isEmpty(rows.dbData) && !_.isEmpty(rows.dbData[0])) {
const resultList = []
rows.dbData.pop()
var bar = new Promise((resolve, reject) => {
rows.dbData[0].forEach((element, index, array) => {
let query = `select * from YCW.YWFWIC ic where ic.witem=${element.witem} and ic.reqno=${element.reqno};`
connectDatabase(query).then((data) =>{
for (var i = 0; i < data.dbData.length; i++) {
element[data.dbData[i]["cfield"]] = data.dbData[i]["cvalue"]
}
resultList.push(element)
// console.log(resultList)
}).catch((err) => {
console.log(err)
})
if (index === array.length -1) resolve();
});
});
bar.then(() => {
console.log(resultList);
});
res.status(msgCodeJson.ERR004.code).send({
result: resultList })
} else {
console.log("empty array")
res.status(msgCodeJson.ERR004.code).send({
message : "No data found"
})
// httpResponseHandlerError(res, msgCodeJson.ERR001.code, msgCodeJson.ERR001.msg)
}
}).catch(() => {
httpResponseHandlerError(res, msgCodeJson.ERR002.code, msgCodeJson.ERR002.msg)
})
} catch (err) {
httpResponseHandlerError(res, msgCodeJson.ERR009.code, msgCodeJson.ERR009.msg)
}
}
module.exports.getDetails = getDetails
i want data to be fit in resultlist but i get empty list after all operation.
while in foreach loop i am getting proper output.
kindly help in issue.
i tried with async foreach loop but some syntax error is coming.
kindly help
as mentioned in the comment of the code you're using
Best way to wait for .forEach() to complete
This is OK if there is no async processing inside the loop.
yet you have an async function inside your forEach callback, namly this:
connectDatabase(query).then((data) => {
for (var i = 0; i < data.dbData.length; i++) {
element[data.dbData[i]["cfield"]] = data.dbData[i]["cvalue"]
}
resultList.push(element)
}).catch((err) => {
console.log(err)
})
you'll need to resolve the "outer/parent" promise from inside the "inner/child" promise
I suggest using a regular good old for loop and/or checking the count of resolved promises against the rows.dbData[0].length and calling a final code/function once they match

Why on my NodeJS+Express REST API a promise calling my function fails while the same promise with setTimeout works?

I have a NodeJS+Express REST API method executing reverse geocoding (using Google's Maps API).
I'm trying to solve it with Promises but the 'then' is getting executed before my function returns with the answers from Google.
When testing the same code just calling a setTimeout, it works as expected. Please see comments in the code (simplify version).
app.get('/api/v1/events', verifyToken, async (req, res) => {
await db.poolPromise.then(pool => {
return pool.request()
.input('UserId', db.sql.UniqueIdentifier, res.authData.userId)
.input('DateFrom', db.sql.DateTime2(7), req.query.dateFrom)
.input('DateTill', db.sql.DateTime2(7), req.query.dateTo)
.output('UserIdAuthorized', db.sql.Bit)
.execute('sp')
}).then(result => {
let output = (result.output || {})
if (!output.UserIdAuthorized) {
res.sendStatus(403)
}
else if (result.recordset.length > 0) {
(new Promise( (resolve) => {
//resolve(123) // this one works as expected
//setTimeout(resolve, 3000, 'temp success') // this one works as expected
// *** this one get passed and the following then is being executed before it answers ***
resolve( getAddress_TEST(result.recordset) )
// **************************************************************************************
})).then(function (value) {
res.json(
{
meta: { count: 10 }, //this is just a sample
result: value // *** this one fails with undefined ***
})
})
} else {
res.sendStatus(404)
}
}).catch(err => {
res.sendStatus(500)
console.error(err)
})
});
const nodeGeocoder_options = {
provider: 'google',
apiKey: process.env.GOOGLE_API_KEY
}
async function getAddress_TEST(recordset) {
//sample recordset for debugging - as you dont have my database
recordset = [{'eventId':14205556,'Lat':54.57767,'Lon':-2.4920483},{'eventId':14205558,'Lat':54.57767,'Lon':-2.492048},{'eventId':14205579,'Lat':53.416908,'Lon':-2.952071},{'eventId':14205588,'Lat':52.644448,'Lon':-1.153185},{'eventId':14205601,'Lat':52.29174,'Lon':-1.532283},{'eventId':14205645,'Lat':52.644448,'Lon':-1.153185},{'eventId':14205801,'Lat':53.68687,'Lon':-1.498708},{'eventId':14206041,'Lat':51.471521,'Lon':-0.2038033},{'eventId':14206049,'Lat':51.471521,'Lon':-0.2038033},{'eventId':14206072,'Lat':51.471521,'Lon':-0.2038033}]
let geocoder = nodeGeocoder(nodeGeocoder_options)
let ps = []
for (var i = 0, length = recordset.length; i < length; i++) {
if (i == 0 || !(i > 0
&& recordset[i - 1].Lat == recordset[i].Lat
&& recordset[i - 1].Lon == recordset[i].Lon)) {
ps.push(new Promise(function (resolve) {
resolve(reverseGeocode(geocoder, recordset[i].Lat, recordset[i].Lon))
}))
} else {
ps.push('-')
}
}
await Promise.all(ps)
.then(function (values) {
for (var i = 0, length = values.length; i < length; i++) {
if (values[i] != '-') {
recordset[i].locationAddress = values[i]
} else {
recordset[i].locationAddress = recordset[i - 1].locationAddress
}
}
}).then(function () {
recordset.forEach(function (v) {
delete v.Lat
delete v.Lon
});
console.log(recordset)
return recordset
})
};
async function reverseGeocode(geocoder, lat, lon) {
let address = '+'
if (lat != 0 && lon != 0) {
await geocoder.reverse({ lat: lat, lon: lon })
.then(res => {
address = res[0].formattedAddress
})
.catch(err => {
console.error(err)
});
}
return address
};
I'm sure it is something simple that I'm missing here...
The basic problem is that your getAddress_TEST function returns a promise that fulfills with nothing (undefined), because it does not contain a return statement. The return recordset is in a then() callback, from where it affects the promise resolution of the awaited promise, but that result is thrown away.
If you want to use async/await, you should get rid of any new Promise and then calls:
app.get('/api/v1/events', verifyToken, async (req, res) => {
try {
const pool = await db.poolPromise
const result = await pool.request()
.input('UserId', db.sql.UniqueIdentifier, res.authData.userId)
.input('DateFrom', db.sql.DateTime2(7), req.query.dateFrom)
.input('DateTill', db.sql.DateTime2(7), req.query.dateTo)
.output('UserIdAuthorized', db.sql.Bit)
.execute('sp')
let output = (result.output || {})
if (!output.UserIdAuthorized) {
res.sendStatus(403)
} else if (result.recordset.length > 0) {
const value = await getAddress_TEST(result.recordset)
res.json({
meta: { count: 10 }, //this is just a sample
result: value
})
} else {
res.sendStatus(404)
}
} catch(err) {
res.sendStatus(500)
console.error(err)
}
});
const nodeGeocoder_options = {
provider: 'google',
apiKey: process.env.GOOGLE_API_KEY
}
async function getAddress_TEST(recordset) {
const geocoder = nodeGeocoder(nodeGeocoder_options)
const ps = recordset.map((record, i) => {
if (i == 0 || !(i > 0
&& recordset[i - 1].Lat == record.Lat
&& recordset[i - 1].Lon == recordLon)) {
return reverseGeocode(geocoder, recordset[i].Lat, recordset[i].Lon))
} else {
return '-'
}
});
const values = await Promise.all(ps)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
for (var i = 0, length = values.length; i < length; i++) {
if (values[i] != '-') {
recordset[i].locationAddress = values[i]
} else {
recordset[i].locationAddress = recordset[i - 1].locationAddress
}
}
recordset.forEach(function (v) {
delete v.Lat
delete v.Lon
});
console.log(recordset)
return recordset
// ^^^^^^^^^^^^^^^^
}
async function reverseGeocode(geocoder, lat, lon) {
if (lat != 0 && lon != 0) {
const res = await geocoder.reverse({ lat: lat, lon: lon })
return res[0].formattedAddress
}
return '+'
}

Promise looping over nested arrays

I'm wrestling with nested promise loops and having trouble finding a working solution.
I looked around and found this: https://stackoverflow.com/a/29396005/3560729
The promiseWhile function seems to have what I need but I'm having trouble getting the nesting to return to the outer loop
promiseWhile:
function promiseWhile(predicate, action) {
function loop() {
if (!predicate()) return;
return Promise.resolve(action()).then(loop);
}
return Promise.resolve().then(loop);
}
Nested Loop:
let outerArray = outerArrayOfObjects;
let returnArray = [];
let returnArrayIndex = 0;
let outerIndex = 0;
let outerLength = outerArray.length;
let passObject = { };
promiseWhile(function() {
return outerIndex < outerLength;
}, function() {
let innerIndex = 0;
let innerLength = outerArray[outerIndex].innerArray.length;
passObject = {
innerObject: outerArray[outerIndex].innerArray[innerIndex],
returnArray: returnArray,
returnArrayIndex: returnArrayIndex
};
promiseWhile(function() {
return innerIndex < innerLength;
}, function() {
return new Promise(function(resolve, reject){
Promise.all([
promiseFunction1(innerObject),
promiseFunction2(innerObject),
promiseFunction3(innerObject),
])
.then(function (allMappings) {
passObject.returnArray[returnArrayIndex++] = {
"result1": allMappings[0],
"result2": allMappings[1],
"result3": allMappings[2]
}
offersIndex++;
return resolve(passObject)
})
.catch(function (err) {
offersIndex++;
return reject(err);
})
})
})
outerIndex++;
}).then(function() {
return resolve(passObject);
});
})
}
I think my main questions are: Where do I process the results? How should I pass the values such that the return array is built properly?
The promiseWhile above is good for performing actions but not good for setting values in a nested loop and returning the results.
I ended up going with an approach using bluebird:
var Promise = require('bluebird');
let outerArray = object.outerArray;
let returnArray = [];
let returnIndex = 0;
Promise.map(outerArray, function (outerArrayObject) {
let innerArray = outerArrayObject.innerArray;
let outerArrayValue = outerArrayObject.value;
Promise.map(innerArray, function (innerArrayObject) {
Promise.all([
PromiseFunction1(innerArrayObject),
PromiseFunction2(innerArrayObject),
PromiseFunction3(innerArrayObject),
])
.then(function (allResults) {
returnArray[returnIndex++] = {
"result1": allResults[0],
"result2": allResults[1],
"result3": allResults[2],
"result4": outerArrayValue,
}
return resolve(returnArray);
})
.catch(function (err) {
return reject(err);
})
})
})
.then(function () {
return resolve(returnArray)
}
).catch(function(err){
return reject(err);
}
)
}

processing items in a array with delay between each item in nodejs

I have a list of messages that needs to send with 1 second delay in between.
currently I am using this function :
send() {
return new Promise((resolve, reject) => {
this._send(resolve);
});
}
_send(resolve, index) {
index = index || 0;
if (this.messages.length && index < this.messages.length) {
if (this.messages[index]) {
let response = this.messages[index];
response.send().then(() => {
this._schedule(resolve, index);
});
} else
this._schedule(resolve, index);
}
else
resolve();
}
_schedule(resolve, index) {
setTimeout(() => {
this._send(resolve, ++index);
}, 1000);
}
Is this s good approach? I have noticed that node is using more ram than usual when this section is running.
I am using Bluebird for Promise.
UPDATE based on #Roamer's comment:
Based on docs :
Promise.reduce(
Iterable<any>|Promise<Iterable<any>> input,
function(any accumulator, any item, int index, int length) reducer,
[any initialValue]
) -> Promise
_wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
__send() {
return Promise.reduce(this.messages, function (dummy, response, index, len) {
return response.send().then(() => {
return this._wait(1000);
}).catch(() => {
//don't care about a error , continue to the next one
return this._wait(1000);
});
}).then(() => {
return true;
});
}
Is this correct?

Herarchy query using sequelize / nodejs

I am trying to load a hierarchy in my database. I have a column with parentId in my table so every row can have a parent. But I am having problems using recursion and promises.
function read (options) {
return serviceItemAttributeModel.findOne({
id: options.id,
id_organization: options.idOrganization
})
.then((attribute) => {
if (attribute) {
return loadChildren(attribute, attribute);
} else {
return attribute;
}
});
}
function loadChildren (root, attribute) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
if (!attributes) {
return root;
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each);
});
}
});
}
So, I call read that calls loadChildren to recursively try to load all entities (by looking children of an entity) and I get an undefined value. Any ideas?
I am also getting an error on console: a promise was created in a handler but was not returned from it.
EDIT:
Came up if this solution after Nosyara help. thanks!:
function read (options) {
return serviceItemAttributeModel.findOne({
where: {
id: options.attributeId,
id_organization: options.idOrganization
}
})
.then((attribute) => {
if (!attribute) {
return new Promise(function (resolve, reject) {
resolve(attribute);
});
} else {
return new Promise(function (resolve, reject) {
attribute.queryCount = 1;
resolve(attribute);
})
.then((attribute) => loadChildren(attribute, attribute));
}
});
}
function loadChildren (root, attribute) {
return new Promise(function (resolve, reject) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
attributes.length = attributes.length || 0;
root.queryCount = root.queryCount - 1 + attributes.length;
if (root.queryCount === 0) {
resolve(root);
} else if (root.queryCount > 10) {
let error = new Error('Service attribute hierarchy cant have more then 10 levels');
error.statusCode = 500;
reject(error);
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each).then(() => {
resolve(root);
});
});
}
});
});
}
You messing up with async calls and returns. You can convert both function to async, and pass through result structure to be updated. Example:
function read(...) {
return new Promise(function (accept, reject) {
// You code goes here, but instead of return
accept(resultFromAsyncFunction);
});
}
// ...
read(...).then(function(resultData) { ... });
Here is example of Promise recursion.

Resources