Nesting warning - avoid nesting problems - javascript

I have tried to fix the nesting problems but nothing I have used works, not even Google Cloud Functions - warning Avoid nesting promises promise/no-nesting.
How can I restructure this method? Below is the code.
exports.payout = functions.https.onRequest((request, response) => {
var uid = "nYIAHSYimJMHbMkXqDt9PQ0U3Nf2";
getPayoutsPending(uid).then((array) => {
getPayoutsAmount(array).then((value) => { **// avoid nesting promises**
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: "me#gmail.com",
note: "Thank you.",
sender_item_id: "Payment"
}
]
});
paypal.payout.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
response.status('500').end();
throw error;
} else {
console.info("payout created");
console.info(payout);
**// avoid nesting problems**
updatePaymentsPending(uid, sender_batch_id).then(() => {
response.status('200').end();
return;
}).catch((error) => {
return console.error(error);
})
}
});
return null;
}).catch((error) => {
return console.error(error);
})
return null;
}).catch((error) => {
return console.error(error);
})
});
The lines marked // avoid nesting promises are the problems.
EDIT - Result from Answer
line 111:20 reads:
return paypal.payout.create(payReq, sync_mode, (error, payout) => {
line 120:21 reads:
}).then(() => {
EDIT #2
After changing the code to what #imjared provided, i am getting the following errors:
ReferenceError: sender_batch_id is not defined
at exports.payout.functions.https.onRequest (/user_code/index.js:136:40)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /var/tmp/worker/worker.js:689:7
at /var/tmp/worker/worker.js:673:9
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
then:
Function execution took 1327 ms, finished with status: 'crash'
then:
ReferenceError: paymentRequest is not defined
at Promise (/user_code/index.js:111:17)
at buildPaymentRequest (/user_code/index.js:90:14)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
EDIT #3 - response from destenson post
Code I have:
exports.payout = functions.https.onRequest((request, response) => {
return getPayoutsPending(request.body.uid)
.then(array => getPayoutsAmount(array))
.then(value => {
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: request.body.email,
note: "Thank you.",
sender_item_id: "Payment"
}
]
});
return paypal.payout.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
response.status('500').end();
throw error;
}
console.info("payout created");
console.info(payout);
return updatePaymentsPending(request.body.uid, sender_batch_id)
}).then(() => {
response.status('200').end();
return null;
});
})
.catch(error => {
console.error(error);
});
});
When app is executed, the functions logs show this:
TypeError: Cannot read property 'then' of undefined
at getPayoutsPending.then.then.value (/user_code/index.js:120:15)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
then:
{ batch_header:
{ payout_batch_id: '*************',
batch_status: 'PENDING',
sender_batch_header:
{ sender_batch_id: '************',
email_subject: 'You have a payment' } },
links:
[ { href: 'https://api.sandbox.paypal.com/v1/payments/payouts/*******',
rel: 'self',
method: 'GET',
encType: 'application/json' } ],
httpStatusCode: 201 }
then:
uncaught exception
then:
ReferenceError: uid is not defined
at paypal.payout.create (/user_code/index.js:119:46)
at IncomingMessage.<anonymous> (/user_code/node_modules/paypal-rest-sdk/lib/client.js:140:13)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
lastly:
Function execution took 1517 ms, finished with status: 'crash'
EDIT #4 - final result
After executing application, the following log from function is:

I would solve this problem by chaining the promises, instead of nesting them. When you return a value from a then() callback, it becomes a new promise that can be then be used again.
I have not tested this modified version of your code, but I hope you get the gist of it:
exports.payout = functions.https.onRequest((request, response) => {
var uid = "nYIAHSYimJMHbMkXqDt9PQ0U3Nf2";
return getPayoutsPending(uid)
.then(array => getPayoutsAmount(array))
.then(value => {
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: "me#gmail.com",
note: "Thank you.",
sender_item_id: "Payment"
}
]
});
return paypal.payout.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
response.status('500').end();
throw error;
}
console.info("payout created");
console.info(payout);
return updatePaymentsPending(uid, sender_batch_id)
});
}).then(() => {
response.status('200').end();
return null;
}).catch(error => {
console.error(error);
});
});
I hope this helps.
EDIT: the successful case was missing a return null. I guess your linter is picky about that.
EDIT: un-nested last then().

Also untested but it seems like the goal based on your eslint is to un-nest everything. This gets kind of cumbersome but it's doable, I guess.
exports.payout = functions.https.onRequest((request, response) => {
var uid = "nYIAHSYimJMHbMkXqDt9PQ0U3Nf2";
// Returns paymentRequest
const buildPaymentRequest = (value) => {
return new Promise((resolve) => {
var valueTrunc = parseFloat(Math.round(value * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: "me#gmail.com",
note: "Thank you.",
sender_item_id: "Payment"
}]
});
resolve(paymentRequest);
});
}
// Returns payout
const createPayout = (paymentRequest) => {
return new Promise((resolve, reject) => {
paypal
.payout
.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
reject(error);
} else {
console.info("payout created");
resolve(payout);
}
});
});
};
getPayoutsPending(uid)
.then(getPayoutsAmount)
.then(buildPaymentRequest)
.then(createPayout)
.then(updatePaymentsPending(uid, sender_batch_id))
.then(() => {
response.status('200').end();
return;
})
.catch((err) => {
console.log(err);
response.status('500').end();
return console.error(error);
})
});
Alternatively, throwing an // eslint-disable at the top of the file would fix your issue ;)

Related

Async function returns undefined even though values are returned

I have the following two functions one calling the other but the record variable is undefined followed by errors. I can't figure out why the script doesn't wait. It seems to just proceed with the undefined variable.
async function searchRecord(recordID) {
client.search({
index: 'records',
type: 'record',
body: {
query: { match: { _id: recordID } }
}
}).then(result => {
return result
}).catch(error => {
console.log(error)
return []
})
}
function test(jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID).then(record => {
if (record.length === 0) {
record = jsonRecord
}
})
}
The error that I get is:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
This is asynchronous, try using await.
async function searchRecord(recordID) {
try {
const result = await client.search({
index: 'records',
type: 'record',
body: {
query: {
match: { _id: recordID }
}
}
});
return result;
} catch (error) {
console.log(error);
return [];
}
}
Try updating searchRecord to return:
async function searchRecord(recordID) {
return client
.search({
index: "records",
type: "record",
body: {
query: {
match: { _id: recordID },
},
},
})
.then((result) => {
return result;
})
.catch((error) => {
console.log(error);
return [];
});
}
The function client.search() returns a promise. You could choose to return that promise as is from searchRecord(). And then, handle the catch in your test() function.
Alternatively you could also handle the error inside searchRecord() as well by implementing a try catch block. But the key in this case is to wait for client.search() to finish before returning from searchRecord().
function searchRecord(recordID) {
return client.search({
index: 'records',
type: 'record',
body: {
query: { match: { _id: recordID } }
}
});
}
function test(jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID).then(record => {
if (record.length === 0) {
record = jsonRecord
}
}).catch(error => {
console.log(error)
return []
})
}
The error that I get is: UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
The reason for this is that searchRecord() returns promise that immediately resolves to undefined. There is no return statement in the function searchRecord().
why you not using Promise? it's ok if you want to use async-await like answers above but using Promise its became very easy
function searchRecord (recordID) {
return new Promise((resolve, reject)=>{
client.search({
index: 'records',
type: 'record',
body: {
query: {
match: { _id: recordID }
}
}
}).then(
result => resolve(result)
).catch(
error => {console.log(error);reject());
});
}
function test (jsonRecord) {
const userID = jsonRecord.users[0]
searchRecord(jsonRecord.objectID)
.then(
record => {
if (record.length === 0) {
record = jsonRecord
}
}
)
}

AssertionError: expected { status: 'SUCCESS', data: [] } to equal { Object (status, data) }

I am running unit test for API call that is serving post request. I am passing request body and must get back response as account data. But I am getting only assertion error
Note: The data is fetched from Azure
spec.js
const accounts=require('./accounts');
const should=require('chai').should();
const chai=require('chai');
const chaiAsPromised=require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
....
beforeEach(function()
{
mockResponse=
[
{
"AccountId": "xyz",
"AccountState": "Active"
}
]
it('Should get account from Azure API', function() {
return accounts.getActivatedAccounts(req.body.customerNumber).
should.eventually.equal(mockResponse);
});
**JavascriptFile**
function getActivatedAccounts(accounts) {
let promise = new Promise(function(resolve, reject) {
fetch(Url , { headers: config.headersAPIM})
.then(response => response.json())
.then(accounts => {
if (accounts) {
Accounts[accounts] = [];
for (account in accounts) {
let accountType = accounts[account]['type]'];
Accounts[email].push(accounts[account]);
}
let reply = {
status : "SUCCESS",
data : Accounts[accounts]
}
resolve(reply);
} else {
let reply = {
status : "SUCCESS",
data : accounts
}
resolve(reply);
}
})
.catch(err => {
console.log("Error: Could not find accounts");
console.log('Error:' + err);
let reply = {
status:"FAILURE",
err: "Error: Could not find accounts. " + err
}
resolve(reply);
})
});
return promise;
}
I am not able to give javascript file that is calling the service, I will give it in the answer section
Sounds like you're asking about the Chai assertion.
equal uses strict equality so unless the two objects are literally the same object it will fail.
eql uses a deep equality comparison and will pass if the objects have the same properties and values.
Here is a simple example:
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
const getActivatedAccounts = () =>
Promise.resolve({ status: 'SUCCESS', data: ['some', 'data'] });
it('Should get account from Azure API', function () {
return getActivatedAccounts()
.should.eventually.eql({ status: 'SUCCESS', data: ['some', 'data'] }); // Success!
});

How can I repair my code? Warning: a promise was created

For some time now I have such a mistake in the console and I do not really understand what is going on and how I should fix it. Error:
can someone show me how to write it better?
Warning: a promise was created in a handler at /Users/pietrzakadrian/Desktop/bankApplicationOfficial/server/controllers/user.controller.js:119:16 but was not returned from it, see
at Function.Promise.attempt.Promise.try (/Users/pietrzakadrian/Desktop/bankApplicationOfficial/node_modules/bluebird/js/release/method.js:29:9)
and this is my code and I added a comment to the line where there is supposed to be a mistake?
// Login Action
exports.login = (req, res) => {
function getTodayDate() {
const today = new Date();
return today;
}
function getToken(userId) {
const token = jwt.sign(
{
id: userId,
},
env.SECRET_KEY,
{
expiresIn: '60min',
},
);
return token;
}
User.findOne({
where: {
login: req.body.login,
},
})
.then(isUser => {
if (isUser) {
if (bcrypt.compareSync(req.body.password, isUser.password)) {
User.update( // <- this
{
last_present_logged: getTodayDate(),
},
{ where: { login: req.body.login } },
).then(() => {
res.status(200).json({
success: true,
token: getToken(isUser.id),
});
});
} else {
User.update(
{
last_failed_logged: getTodayDate(),
},
{ where: { login: req.body.login } },
).then(() => {
res.status(200).json({
error: 'Auth failed. The password is incorrect.',
success: false,
});
});
}
} else {
res
.status(200)
.json({ error: 'Auth failed. User does not exist', success: false });
}
})
.catch(() => {
/* just ignore */
});
};
Bluebird's documentation (check out the examples at : Warning explanations section says :
This usually means that you simply forgot a return statement somewhere, which will cause a runaway promise that is not connected to any promise chain.

Best Practice for chaining promises in Nodejs/Express

I know that best way to chain promises in Nodejs/Express is like:
doSomeThing()
.then()
.then()
.catch();
But recently had to use the async and q module to iterate over a list/array and run an async function. I wanted to know that is there better way of doing/writing this -
var deferred = Q.defer();
var deferred2 = Q.defer();
models.Local.findOne({
where: {
id: parseInt(req.body.localid)
}
})
.then(function(resultLocal){
if(!resultLocal){
return res.status(404).json(
{
"status" : "error",
'error': "Local Not Found"
});
}
return models.Documents.create(req.body.document);
})
.then(function(docCreated){
var attributes = req.body.document.Attributes;
async.each(attributes, function(item, callback) {
models.Doc_Tags.create({
value: item.value,
attribute_id: item.id,
document_id: docCreated.id
})
.then(function(attributeCreated){
var upObj = {};
upObj[item.col_name] = item.value;
models[item.table_name].update(upObj,{
where:{
id: req.body.document.local_id
}
})
.then(function(primaryUpdated){
deferred2.resolve();
})
.catch(function(error){
return res.status(400).json({status: 'error', error:error.message});
});
deferred2.promise
.then(function(){
callback();
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
})
.catch(function(error){
return res.status(400).json({status: 'error', error:error.message});
});
}, function(err,r){
if( err ) {
return res.status(400).json({status: 'error', error:err.message});
} else {
console.log('All attributes Associated');
deferred.resolve(docCreated);
}
});
deferred.promise.then(function(result, attributes){
var obj = req.body.Local;
models.Local.update(obj, {
where: {
id: result.local_id
}
})
.then(function(resultUpdate){
return res.status(201).json({status: "success", document: result});
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
})
.catch(function(error){
return res.status(400).json({status: "error", error: error.message});
});
Please correct me if I am doing something wrong. Functionality wise the code is running properly but I think I can refactor it somehow to look and read better.
Thanks.
your code can be cleaner and shorter.
basic ideas are
turn callback to promise, e.g., promisify() of bluebird.js can do that
async.each part can be refactor to Promise.all to call promise parallelly
re-arrange .then chain
javascript es6 is cleaner than older version
sample refactored version
const Promise = require('bluebird')
// CustomError should be separated to another node module
class CustomError {
constructor(message, code) {
this.code = code
this.message = message
}
}
let docCreated = undefined
function getPromiseParams(item) {
return Promise.try(() => {
return models.Doc_Tags.create({
value: item.value,
attribute_id: item.id,
document_id: docCreated.id
})
}).then(attributeCreated => {
const upObj = {};
upObj[item.col_name] = item.value;
return models[item.table_name].update(upObj, { where:{ id: req.body.document.local_id } })
}).then(primaryUpdated => {
return docCreated
}).catch(error => {
throw new CustomError(error.message, 400)
})
}
Promise.try(() => {
return models.Local.findOne({ where: { id: parseInt(req.body.localid) } })
}).then(resultLocal => {
if(!resultLocal) throw new CustomError('Local Not Found', 404)
return models.Documents.create(req.body.document)
}).then(_docCreated => {
docCreated = _docCreated // assign value to docCreated
const attributes = req.body.document.Attributes
const promiseParams = attributes.map(item => getPromiseParams(item))
return Promise.all(promiseParams)
}).then(() => {
const obj = req.body.Local
return models.Local.update(obj, { where: { id: result.local_id }})
}).then(() => {
return res.status(201).json({status: "success", document: docCreated})
}).catch(error => {
return res.status(error.code || 400).json({status: "error", error: error.message});
})

ES7 timeout for async/await fetch

I want to set timeout in my fetch method, and I follow this github issue.
from #mislav answer, we could custom timeout method like below.
// Rough implementation. Untested.
function timeout(ms, promise) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error("timeout"))
}, ms)
promise.then(resolve, reject)
})
}
timeout(1000, fetch('/hello')).then(function(response) {
// process response
}).catch(function(error) {
// might be a timeout error
})
and improved by #nodkz
function timeoutPromise(ms, promise) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error("promise timeout"))
}, ms);
promise.then(
(res) => {
clearTimeout(timeoutId);
resolve(res);
},
(err) => {
clearTimeout(timeoutId);
reject(err);
}
);
})
}
but I want to call that method in ES7 async/await syntax, I try below way but failed.
async request() {
try {
let res = await timeout(1000, fetch('/hello'));
} catch(error) {
// might be a timeout error
}
}
and how could I use it in ES7 async/await syntax?
thanks for your time.
update
thanks for #Bergi reply, and I display my code in http request step.
ES7 fetch
'use strict';
import { configApi, } from 'tyrantdb-config';
import { timeoutPromise, } from 'xd-utils-kit';
const keyResponse = configApi.keyResponse;
const KEY_DATA = keyResponse.data;
module.exports = {
async fetchLogin(url, params, dataRely) {
let {
self, processor, route,
storage, isLocalStoraged,
email, password, _warning, } = dataRely;
self.setState({ isWait: true, });
try {
let res = await timeoutPromise(10, fetch(url, params));
if (res.status >= 200 && res.status < 300) {
let resJson = await res.json();
let resData = resJson[KEY_DATA];
let cache = {
...resData,
password,
};
if (isLocalStoraged !== true) {
processor(cache, storage);
}
global.g_user = cache;
self.setState({ isWait: false, });
// clean user info which already bind to self[this]
self.email = self.password = null;
self.isLocalStoraged = false;
route();
} else {
console.log(`[login] response code: ${res.status}`);
self.setState({ isWait: false, });
_warning();
}
} catch(error) {
console.error(error);
}
},
saveLoginState: (cache, storage) => {
storage.save({
key: 'loginState',
rawData: cache,
});
},
openURL: (url, Linking) => {
Linking.canOpenURL(url).then(supported => {
if (!supported) {
console.log(`can\'t handle url: ${url}`);
} else {
return Linking.openURL(url);
}
}).catch((err) => {
console.log(`error occurred: ${err}`);
})
},
};
ES6 fetch
'use strict';
import { configApi, } from 'tyrantdb-config';
import { timeoutPromise, } from 'xd-utils-kit';
const keyResponse = configApi.keyResponse;
const KEY_DATA = keyResponse.data;
module.exports = {
fetchLogin(url, params, dataRely) {
let {
self, processor, route,
storage, isLocalStoraged,
email, password, _warning, } = dataRely;
self.setState({ isWait: true, });
timeoutPromise(1000, fetch(url, params))
.then((res) => {
if (res.status >= 200 && res.status < 300) {
return res.json();
} else {
console.log(`[login] response code: ${res.status}`);
self.setState({ isWait: false, });
_warning();
}
})
.then((resJson) => {
let resData = resJson[KEY_DATA];
let cache = {
...resData,
password,
};
if (isLocalStoraged !== true) {
processor(cache, storage);
}
global.g_user = cache;
self.setState({ isWait: false, });
// clean user info which already bind to self[this]
self.email = self.password = null;
self.isLocalStoraged = false;
route();
})
.catch(error => console.error(error))
.done();
},
saveLoginState: (cache, storage) => {
storage.save({
key: 'loginState',
rawData: cache,
});
},
openURL: (url, Linking) => {
Linking.canOpenURL(url).then(supported => {
if (!supported) {
console.log(`can\'t handle url: ${url}`);
} else {
return Linking.openURL(url);
}
}).catch((err) => {
console.log(`error occurred: ${err}`);
})
},
};
above ES6 fetch will catch output below, I think #Bergi is right, It's my code fault, not timeoutPromise error.
2016-06-08 22:50:59.789 [info][tid:com.facebook.React.JavaScript] [login] response code: 400
2016-06-08 22:50:59.808 [error][tid:com.facebook.React.JavaScript] { [TypeError: undefined is not an object (evaluating 'KEY_DATA')]
line: 103795,
column: 29,
sourceURL: 'http://172.26.129.189:8081/index.ios.bundle?platform=ios&dev=true' }
2016-06-08 22:50:59.810 [error][tid:com.facebook.React.JavaScript] One of the sources for assign has an enumerable key on the prototype chain. This is an edge case that we do not support. This error is a performance optimization and not spec compliant.
2016-06-08 22:50:59.810 [info][tid:com.facebook.React.JavaScript] 'Failed to print error: ', 'One of the sources for assign has an enumerable key on the prototype chain. This is an edge case that we do not support. This error is a performance optimization and not spec compliant.'

Categories

Resources