Look for Promise bluebird code review for node.js - javascript

When and where need to use new Promise(Function<Function resolve, Function reject> resolver) -> Promise
My Sample code:
userInfo.js
var Promise = require('bluebird');
var winston = require('winston');
var _ = require('lodash');
var request = Promise.promisify(require("request"));
exports.getWeather = function (data) {
var cityName = data.userProfile.city;
return request("http://0.0.0.0:3003/api/Weather/byCity?city=" + cityName).spread(function (res, body) {
var result = JSON.parse(body).data;
return _.merge(data, result);
});
};
exports.getUserProfile = function (userId) {
return new Promise(function (resolve, reject) {
request("http://0.0.0.0:3003/api/UserProfile/getUserProfile?id=" + userId).spread(function (res, body) {
var result = JSON.parse(body).data;
resolve(result);
});
})
};
exports.getEvents = function (data) {
var cityName = data.userProfile.city;
return request("http://0.0.0.0:3003/api/Events/byCity?city=" + cityName).spread(function (res, body) {
var result = JSON.parse(body).data;
return _.merge(data, result);
});
};
exports.getFashion = function (data) {
var gender = data.userProfile.gender;
return request("http://0.0.0.0:3003/api/Fashion/byGender?gender=" + gender).spread(function (res, body) {
var result = JSON.parse(body).data;
return _.merge(data, result);
});
};
exports.displayDetail = function (data) {
console.log(data);
};
Above code I try call in 2 way in promise
getUserProfile.js
var userInfo = require('./userInfo');
module.exports = function(){
return userInfo.getUserProfile(3)
.then(userInfo.getFashion)
.then(userInfo.getEvents)
.then(userInfo.getWeather)
.then(userInfo.displayDetail)
.catch(function (e) {
console.log('Error:');
console.error(e.stack)
})
.finally(function () {
console.log('done');
});
}
2nd way:
getUserInformation.js
var userInfo = require('./userInfo');
module.exports = function () {
return new Promise(function (resolve, reject) {
resolve(3);
})
.then(userInfo.getUserProfile)
.then(userInfo.getFashion)
.then(userInfo.getEvents)
.then(userInfo.getWeather)
.then(userInfo.displayDetail)
.catch(function (e) {
console.log('Error:');
console.error(e.stack)
})
.finally(function () {
console.log('done');
});
};
getDetails.js
var userInfo = require('./getUserInformation');
userInfo()
.then(function(){
console.log('getDetails done')
})
.catch(function (e) {
console.log('Error:');
console.error(e.stack)
})
.finally(function () {
console.log('done');
});
please let me know what the difference and is there any issues by using these way?

exports.getUserProfile = function (userId) {
return new Promise(function (resolve, reject) {
request("http://0.0.0.0:3003/api/UserProfile/getUserProfile?id=" + userId).spread(function (res, body) {
var result = JSON.parse(body).data;
resolve(result);
});
})
};
Please don't do this. Just return from the callback, and return the promise created by then, like you have done it in your other three methods.
return userInfo.getUserProfile(3)
.then(…)
vs.
return new Promise(function (resolve, reject) {
resolve(3);
})
.then(userInfo.getUserProfile)
.then(…)
Well, the first one is much more readable and concise. They're pretty much equivalent except for the case that getUserProfile does throw synchronously, which it shouldn't anyway. Also in the first case getUserProfile is invoked as a method on userInfo, while in the second case it's just a callback function, the this in the calls will be different.
The second pattern can be tremendously simplified though by using Promise.resolve instead of the new Promise constructor:
return Promise.resolve(3)
.then(userInfo.getUserProfile)
.then(…)
This is totally fine, and aligns better with the rest of the chain. Speaking of which, …
.then(userInfo.getFashion)
.then(userInfo.getEvents)
.then(userInfo.getWeather)
where each of the functions returns a promise that resolves with
additional data merged into its argument
is not exactly the best way to solve this. Yes, it ensures that these three functions are called after each other, and is an acceptable pattern for that case. However, in your case you're mixing the request calls to the API with that argument-extraction and result-merging in the same function; which by the separation of concerns you shouldn't. Rather make the functions pure
exports.… = function (arg) {
return request("http://0.0.0.0:3003/api/…?…=" + arg).spread(function (res, body) {
return JSON.parse(body).data;
});
};
And now you can combine them separately - and not only in sequence, but also in parallel:
userInfo.getUserProfile(3)
.then(function(data) {
var p = data.userProfile;
return Promise.prop({
userProfile: 0,
fashion: userInfo.getFashion(p.gender), // `\
events: userInfo.getEvents(p.city), // }=> execute requests in parallel
weather: userInfo.getWeather(p.city) // ./
});
})
.then(userInfo.displayDetail)
.catch(function (e) {
console.error('Error:', e.stack)
});

The first way is much more readable, and there's no benefit to starting the chain with a promise that returns a constant as in your second way.
They both do effectively the same thing, with one caveat: In your second example (Starting the chain with a Promise), the getUserProfile call will be run on the next tick (Similar to if you'd thrown it in a setTimeout 0) rather than atomically.

Related

How to convert Promise to async await in node js

i am trying to convert different code with promise to async await. as i am new to node and dont have much idea about promise can anybody help me with the following example. my vs code doesn't highlight the promise syntax.
module.exports = {
execSqlQuery: function (procedure, parameters) {
return new Promise(function (fulfill, reject) {
var request = new sql.Request(connPool);
parameters.forEach(function (parameter) {
request.input(parameter.name, parameter.type, parameter.value);
});
request.execute(procedure, function (err, recordSets) {
if (err) {
reject(err);
}
else {
fulfill(recordSets);
}
});
});
},
First, check whether request.execute has a promise version rather than a callback version. If not you can use promisify library to make it a promise.
const util = require('util');
const execute= util.promisify(sql.execute);
const execSqlQuery = async (procedure, parameters) => {
var request = new sql.Request(connPool);
parameters.forEach(function (parameter) {
request.input(parameter.name, parameter.type, parameter.value);
});
var recordSets = await request.execute(procedure)
return recordSets;
}
I'm guessing you using Microsoft SQL then if it is true: https://www.npmjs.com/package/mssql#asyncawait
function async () {
var request = new sql.Request(connPool);
parameters.forEach(function (parameter) {
request.input(parameter.name, parameter.type, parameter.value);
});
var res = await request.execute(procedure, function (err, recordSets);
// now check the result for success or errors!
}
this one worked
const execSqlQuery = async (procedure, parameters) => {
var request = new sql.Request(connPool);
parameters.forEach(function (parameter) {
request.input(parameter.name, parameter.type, parameter.value);
});
var recordSets = await request.execute(procedure)
return recordSets;
}

Getting asynchronous function in external library with callback to work synchronously

I am using a library call to connect to my vendor. The libary call requires a callback in the call. Without a callback in the function, I can easily make this synchronous. With the Callback, everything I do is stuck in the callback and never bubbles it way out.
I have literally tried 100 different ways to get this to work.
function removeFromDNC(emailAddress, accessToken_in)
{
return new Promise( function(resolve, reject)
{
try{
const options =
{
auth: {
accessToken: accessToken_in
}
, soapEndpoint: 'https://webservice.XXX.exacttarget.com/Service.asmx'
};
var co = {
"CustomerKey": "DNC",
"Keys":[
{"Key":{"Name":"Email Address","Value": emailAddress}}]
};
var uo = {
SaveOptions: [{"SaveOption":{PropertyName:"DataExtensionObject",SaveAction:"Delete"}}]
};
const soapClient = new FuelSoap(options);
//again, I don't control the structure of the next call.
let res = soapClient.delete('DataExtensionObject', co, uo, async function( err, response ) {
if ( err ) {
// I can get here, but my reject, or if I use return, does nothing
reject();
}else{
// I can get here, but my reject, or if I use return, does nothing
resolve();
}
});
console.log("res value " + res); // undefined - of course
}catch(err){
console.log("ALERT: Bad response back for removeFromDNC for email: " + emailAddress + " error: " + err);
console.log("removeFromDNC promise fulfilled in catch");
reject();
}
});
}
Both methods resolve and reject expect parameters, which are res and err in your case.
As far as removeFromDNC returns a Promise instance, you should call it using either async/await syntax:
const res = await removeFromDNC(...);
or chaining then/catch calls:
removeFromDNC(...)
.then((res) => { ... }) // resolve
.catch((err) => { ... }) // reject
EDIT:
If you want to avoid usage of callbacks inside removeFromDNC, consider promisifying of soapClient.delete call. Refer to util.promisify() if you working in Node.js or use own implementation.
Here is the example for demonstration:
const promisify = (fun) => (...args) => {
return new Promise((resolve, reject) => {
fun(...args, (err, result) => {
if(err) reject(err);
else resolve(result);
})
})
}
const soapClient = {
delete: (value, cb) => {
setTimeout(() => cb(null, value), 10);
}
};
async function removeFromDNC(emailAddress, accessToken_in) {
const soapDelete = promisify(soapClient.delete.bind(soapClient));
const res = await soapDelete('Soap Responce');
//You can use res here
return res;
}
removeFromDNC().then(res => console.log(res))

How to pass a variable from a Request as the return value of the global function js

Hello and thank you in advance. I'm using npm tedious package to interact with a database. I'm also using Meteor.call and methods, for which I need to pass a variable (newdata in the example below) that carries the data taken from the database as the return value of the function "rr", so that I would be able to use the result of the function in the client by a Meteor call.
function rr(){
var newdata = [];
var ahora = new Request("SELECT * FROM prueba", function (err) {
if (err) {
console.log("err1");
} else {
}
})
ahora.on('row', function(columns) {
columns.forEach(function(column) {
newdata.push(column.value);
});
});
}
I want "newdata" to be the result of the rr function. How can I do that? If I write "return newdata" it's undefined, I can't use await because newdata is not the return value of any function...
Thank you very much.
Tedious doesn't seem to support promises natively, but you can wrap your function in a promise:
function rr() {
return new Promise((resolve, reject) => {
var ahora = new Request("SELECT * FROM prueba", function (err) {
if (err) {
reject(err);
}
});
var newdata = [];
ahora.on('row', function(columns) {
columns.forEach(function(column) {
newdata.push(column.value);
});
});
resolve(newdata);
}
}
Or slightly shorter:
function rr() {
return new Promise((resolve, reject) => {
new Request("SELECT * FROM prueba")
.on("error", reject)
.on("row", function(columns) {
resolve(columns.map(column => column.value))
});
}
}
If you'd rather not make promises manually, you can try Bluebird's promisify function. I also found a tedious-specific promisifying package tedious-promises, but it doesn't seem to be properly maintained.
You could do something like this:
function rr(){
return await new Promise((resolve, reject) => {
new Request("SELECT * FROM prueba", (err, rowCount) => {err && reject(err);})
.on('row', columns => resolve(columns.map(c => c.value)));
});
}

Programmatically add arguments to a Promise

I need to create a function that receives a Promise as a first parameter and an array of its parameters (if it has at least one) or null if none.
My function looks this way:
var executeMappingStep = (promiseStatus, myPromise, myPromiseParams) => {
return new Promise((resolve, reject) => {
//if the execution was success, there's no need to invoke myPromise again
if(promiseStatus.success == true)
return resolve(promiseStatus.previousResponseSaved);
if (myPromiseParams!= null) {
//how I resolve this logic is my doubt
myPromise.addArrayOfParameters(myPromiseParams);
}
myPromise().then(result => resolve(result)).catch(err => reject(err));
});
};
I was reading about the .bind() function which works for functions but not for promises according to this question. I also saw something about the .apply() function, but this doesn't seems to be what I need.
Edit
My function is invoked like myFirstPromise when there aren't parameters, and like mySecondPromise when I have at least one:
var executePromises = (myFirstPromise, mySecondPromisePromise) => {
return new Promise((resolve, reject) => {
//retrieve an array of every promise status
var promisesStatus = getPromisesStatus();
executeMappingStep(promisesStatus[0], myFirstPromise, null).then(result => {
return convertToXml(result);
}).then(result => {
var parameters = {
value1 : result,
value2: [1,2,3],
value3: "some string",
value4: 10
};
return executeMappingStep(promisesStatus[1], mySecondPromisePromise, parameters);
}).then(result => resolve(result)).catch(err => reject(err));
});
};
var myFirstPromise = function() {
return new Promise(function (resolve, reject) {
//some logic for first promise
});
}
var mySecondPromise = function(firstParam, secondParam, thirdParam, fourthParam) {
return new Promise(function (resolve, reject) {
//some logic for second promise with its params
});
}
Probably, in the function addArrayOfParameters I need to loop into every property on the object (I realized that an array would not work), like this
Is there a way to programmatically add parameters from an array before its resolved?
Edit 2
The reason behind this logic is much complex, but this is pretty much what I need to solve. Basically, in my code, every promise can be executed with errors and there's a logic involved in the executePromises function which returns the status of every promise whether was successfully executed or not. This logic involves more than 10 promises chained, and if some of them fail e.g: promise number 5, then 6,7 etc, will have to be invoked again in some admin backend.
I don't what to replicate the logic for each promise, so that's why I wanted to create the executeMappingStep function to encapsulate that logic.
Probably is hard to explain, I tried to simplify the most as I could. I made a few more changes to explain what I just said.
Probably, I have complicated things a lot here, but anyway, this is the solution that I wanted.
It's good to know that the .then function can invoked in other place and the promises can be created with the needed arguments and then sent as parameter.
With that said:
var executePromises = (myFirstPromise, mySecondPromisePromise) => {
return new Promise((resolve, reject) => {
//retrieve an array of every promise status
var promisesStatus = getPromisesStatus();
executeMappingStep(promisesStatus[0], myFirstPromise()).then(result => {
return convertToXml(result);
}).then(result => {
var mySecondPromisePromiseParameter = mySecondPromise(result, [1,2,3], "some string", 10);
return executeMappingStep(promisesStatus[1], mySecondPromisePromiseParameter);
}).then(result => resolve(result)).catch(err => reject(err));
});
};
An then, the executeMappingStep just receives the promise and invokes the .then internally, this way:
var executeMappingStep = (promiseStatus, myPromise) => {
return new Promise((resolve, reject) => {
//if the execution was success, there's no need to invoke myPromise again
if(promiseStatus.success == true)
return resolve(promiseStatus.previousResponseSaved);
myPromise.then(result => resolve(result)).catch(err => reject(err));
});
};
Probably use async library may resolve your issue. Try to run code on your console and look into that.
With async library:
var async = require("async");
async.waterfall([
function (callback) {
var methodParam = [1, 2, 3];
console.log("first method called");
callback(null, methodParam);
},
function (methodParams, callback) {
var newParams = [4, 5, 6];
newParams = methodParams.concat(newParams);
console.log("second method called", methodParams);
callback(null, newParams);
}
], function (err, res) {
if (err) {
console.log("error occured");
} else {
console.log("final result", res);
}
});
With promise:
var promise1 = (() => {
return new Promise((resolve, reject) => {
var methodParam = [1, 2, 3];
console.log("first promise called");
resolve(methodParam);
});
});
var promise2 = ((methodParams) => {
return new Promise((resolve, reject) => {
var newParams = [4, 5, 6];
newParams = methodParams.concat(newParams);
console.log("second promise called", methodParams);
resolve(newParams);
});
});
promise1()
.then((resPromise1) => {
return promise2(resPromise1);
}).then((resPromise2) => {
console.log("final result", resPromise2);
});

Promisify streams

I'm trying to promisify streams but it appears harder than I expected. Here is my attempt:
'use strict'
const Promise = require('bluebird')
const Twitter = require('twitter')
const TwitterStream = module.exports = function TwitterStream (config) {
// init Twitter Streaming API for OAuth
this.stream = new Twitter({
consumer_key: config.get('/twitter/consumerKey'),
consumer_secret: config.get('/twitter/consumerSecret'),
access_token_key: config.get('/twitter/accessTokenKey'),
access_token_secret: config.get('/twitter/accessTokenSecret')
})
.stream('statuses/filter', {
track: config.get('/twitter/track')
})
}
TwitterStream.prototype.receive = function () {
return new Promise((resolve, reject) => {
this.stream.on('data', resolve).on('error', reject)
})
}
TwitterStream.prototype.destroy = function () {
this.stream.destroy()
}
The main problem is that when I create the object
const stream = new TwitterStream(config)
stream.receive().then((data) => console.log(data))
when I execute only one object is read. no other data are streamed.
TwitterStream.prototype.receive = function () {
return new Promise((resolve, reject) => {
this.stream
.on('data', (data) => resolve(data)
.on('error', (error) => reject(error))
})
}
By using Rx extensions, it's pretty straightforward:
TwitterStream.prototype.receive = function () {
return Rx.Observable.create((observer) => {
this.stream
.on('data', (data) => observer.onNext(data))
.on('error', (err) => observer.onError(err));
});
}
And then
const stream = new TwitterStream(config)
stream.receive().subscribe((data) => console.log(data));
You need to return a promise in the callback of the stream.on function. Right now, the receive method when being called just returns a promise which once resolved returns the value or error.
Here is a not tested and most likely still buggy code to illustrate how you could do it with promises:
function defer() {
var resolve, reject;
var promise = new Promise(function() {
resolve = arguments[0];
reject = arguments[1];
});
return {
resolve: resolve,
reject: reject,
promise: promise
};
}
TwitterStream.prototype.receive = function() {
this.stream
.on('data', data => {
this.dataCache = this.dataCache || [];
this.dataCache.push(data);
this.tryToSendData()
})
.on('end', () => {
this.finished = true;
this.tryToSendData()
})
.on('error', err => {
this.lastError = err;
// error handling still missing
})
return this;
}
TwitterStream.prototype.tryToSendData = function() {
if (this.defered) {
let defered = this.defered;
this.defered = null;
// if data is available or finished then pass the first element of buffer (or undefined)
defered.resolve(this.dataCache.shift())
}
}
TwitterStream.prototype.getNextData = function() {
if (this.dataCache.length > 0 || this.finished) {
// if data is available or finished then pass the first element of buffer (or undefined)
return Promise.resolve(this.dataCache.shift());
} else {
// otherwise we need a defered object
this.defered = defer();
}
}
The usage could then look like this:
stream.receive().getNextData()
.then(function processData(data) {
if (data) {
console.dir(data);
// if data is available then continue requestin the data
return stream.getNextData().then(processData);
}
})
It is a rare case where you could use Deferreds.
I think you might want to take a look at my, already promisified streams in scramjet.
For your Twitter example this code should work well:
const stream = new Twitter({
consumer_key: config.get('/twitter/consumerKey'),
consumer_secret: config.get('/twitter/consumerSecret'),
access_token_key: config.get('/twitter/accessTokenKey'),
access_token_secret: config.get('/twitter/accessTokenSecret')
})
.stream('statuses/filter', {
track: config.get('/twitter/track')
})
.pipe(new scramjet.DataStream)
Then perform any transformations you like... for example map the stream somehow and accumulate the stream into an array when you're done.
stream.map(
function (a) { return modifyTheTweetSomehow(a); } // a Promise can be returned here
).accumulate(
function(a, i) { a.push(i); },
[]
) // this returns a Promise that will be resolved on stream end.
I hope you like it. :)

Categories

Resources