I'm trying to call a promise function recursively.
The following call service.getSentenceFragment() returns upto 5 letters from a sentence i.e. 'hello' from 'helloworld. Providing a nextToken value as a parameter to the call returns the next 5 letters in the sequence. i.e. 'world'. The following code returns 'hellohelloworldworld' and does not log to the console.
var sentence = '';
getSentence().then(function (data)) {
console.log(sentence);
});
function getSentence(nextToken) {
return new Promise((resolve, reject) => {
getSentenceFragment(nextToken).then(function(data) {
sentence += data.fragment;
if (data.nextToken != null && data.nextToken != 'undefined') {
getSentence(data.NextToken);
} else {
resolve();
}
}).catch(function (reason) {
reject(reason);
});
});
}
function getSentenceFragment(nextToken) {
return new Promise((resolve, reject) => {
service.getSentenceFragment({ NextToken: nextToken }, function (error, data) {
if (data) {
if (data.length !== 0) {
resolve(data);
}
} else {
reject(error);
}
});
});
}
Cause when you do this:
getSentence(data.NextToken);
A new Promise chain is started, and thecurrent chain stays pending forever. So may do:
getSentence(data.NextToken).then(resolve, reject)
... but actually you could beautify the whole thing to:
async function getSentence(){
let sentence = "", token;
do {
const partial = await getSentenceFragment(token);
sentence += partial.fragment;
token = partial.NextToken;
} while(token)
return sentence;
}
And watch out for this trap in getSentenceFragment - if data is truthy but data.length is 0, your code reaches a dead end and the Promise will timeout
// from your original getSentenceFragment...
if (data) {
if (data.length !== 0) {
resolve(data);
}
/* implicit else: dead end */
// else { return undefined }
} else {
reject(error);
}
Instead, combine the two if statements using &&, now our Promise will always resolve or reject
// all fixed!
if (data && data.length > 0)
resolve(data);
else
reject(error);
You could recursively call a promise like so:
getSentence("what is your first token?")
.then(function (data) {
console.log(data);
});
function getSentence(nextToken) {
const recur = (nextToken,total) => //no return because there is no {} block so auto returns
getSentenceFragment(nextToken)
.then(
data => {
if (data.nextToken != null && data.nextToken != 'undefined') {
return recur(data.NextToken,total + data.fragment);
} else {
return total + data.fragment;
}
});//no catch, just let it go to the caller
return recur(nextToken,"");
}
function getSentenceFragment(nextToken) {
return new Promise((resolve, reject) => {
service.getSentenceFragment({ NextToken: nextToken }, function (error, data) {
if (data) {
if (data.length !== 0) {
resolve(data);
}
} else {
reject(error);
}
});
});
}
Related
I'm learning JavaScript, and I decided that an excelent chalenge would be to implement a custom Promise class in JavaScript. I managed to implement the method then, and it works just fine, but I'm having difficulties with the error handling and the method catch. Here is my code for the Promise class (in a module called Promise.mjs):
export default class _Promise {
constructor(executor) {
if (executor && executor instanceof Function) {
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
}
resolve() {
if (this.callback && this.callback instanceof Function) {
return this.callback(...arguments);
}
}
reject(error) {
if (this.errorCallback && this.errorCallback instanceof Function) {
return this.errorCallback(error);
} else {
throw `Unhandled Promise Rejection\n\tError: ${error}`;
}
}
then(callback) {
this.callback = callback;
return this;
}
catch(errorCallback) {
this.errorCallback = errorCallback;
return this;
}
}
When I import and use this class in the following code, all the then() clauses run as according, and I get the desired result in the console:
import _Promise from "./Promise.mjs";
function sum(...args) {
let total = 0;
return new _Promise(function (resolve, reject) {
setTimeout(function () {
for (const arg of args) {
if (typeof arg !== 'number') {
reject(`Invalid argument: ${arg}`);
}
total += arg;
}
resolve(total);
}, 500);
});
}
console.time('codeExecution');
sum(1, 3, 5).then(function (a) {
console.log(a);
return sum(2, 4).then(function (b) {
console.log(b);
return sum(a, b).then(function (result) {
console.log(result);
console.timeEnd('codeExecution');
});
});
}).catch(function (error) {
console.log(error);
});
But, when I add an invalid argument to the sum() function, i.e. not a number, the reject() method runs, but it don't stop the then() chain, as should be, and we also get an exception. This can be seen from the following code:
import _Promise from "./Promise.mjs";
function sum(...args) {
let total = 0;
return new _Promise(function (resolve, reject) {
setTimeout(function () {
for (const arg of args) {
if (typeof arg !== 'number') {
reject(`Invalid argument: ${arg}`);
}
total += arg;
}
resolve(total);
}, 500);
});
}
console.time('codeExecution');
sum(1, 3, '5').then(function (a) {
console.log(a);
return sum(2, 4).then(function (b) {
console.log(b);
return sum(a, b).then(function (result) {
console.log(result);
console.timeEnd('codeExecution');
});
});
}).catch(function (error) {
console.log(error);
});
Also, if I catch an error in nested then() methods, the outer catch() doesn't notice this and I get an exception again. The goal is to implement a lightweight functional version of Promises, but not necessarily with all its functionality. Could you help me?
The problem in your code is that your sum function calls both the reject and the resolve functions. There's no handling in the sum function that will cause it not to call the resolve function at the end, and there is nothing in your _Promise that blocks this behavior.
You have 2 options to fix this.
Option 1 would be if you want your _Promise to act like a real Promise you will need to manage a state and once a promise got to a final state stop calling the callback or errorCallback.
Option 2 would be to prevent from calling both reject and resolve in the function calling the _Promise, in this case, the sum function.
With the comments that you guys provide me, I was able to improve the code and correct the errors mentioned, as shown below. Now, I would like you to give me suggestions on how to proceed and improve the code. Thanks. (The code can also be found on github).
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
function _Promise(executor) {
let state = PENDING;
let callOnFulfilled = [];
let callOnRejected = undefined;;
function resolve(...args) {
if (!state) {
state = FULFILLED;
}
resolveCallbacks(...args);
};
function reject(error) {
state = REJECTED;
if (callOnRejected && (callOnRejected instanceof Function)) {
callOnRejected(error);
callOnRejected = undefined;
callOnFulfilled = [];
} else {
throw `Unhandled Promise Rejection\n\tError: ${error}`;
}
};
function resolveCallbacks(...value) {
if (state !== REJECTED) {
let callback = undefined;
do {
callback = callOnFulfilled.shift();
if (callback && (callback instanceof Function)) {
const result = callback(...value);
if (result instanceof _Promise) {
result.then(resolveCallbacks, reject);
return;
} else {
value = [result];
}
}
} while (callback);
}
};
if (executor && (executor instanceof Function)) {
executor(resolve, reject);
}
this.then = function (onFulfilled, onRejected) {
if (onFulfilled) {
callOnFulfilled.push(onFulfilled);
if (state === FULFILLED) {
resolveCallbacks();
}
}
if (onRejected && !callOnRejected) {
callOnRejected = onRejected;
}
return this;
};
this.catch = function (onRejected) {
return this.then(undefined, onRejected);
};
}
function sum(...args) {
let total = 0;
return new _Promise(function (resolve, reject) {
setTimeout(function () {
for (const arg of args) {
if (typeof arg !== 'number') {
reject(`Invalid argument: ${arg}`);
}
total += arg;
}
resolve(total);
}, 500);
});
}
console.time('codeExecution');
sum(1, 3, 5).then(function (a) {
console.log(a);
return sum(2, 4).then(function (b) {
console.log(b);
return sum(a, b).then(function (result) {
console.log(result);
return 25;
});
}).then(function (value) {
console.log(value);
console.timeEnd('codeExecution');
});
}).catch(function (error) {
console.log(error);
});
can someone help me solve this problem. I dont understand why console log and value of it after evaluate different. As you can see this pic below key SeqNo in console.log has no value but it actually has it
This is my function in component in Angular will return array and will be parameter for other function has above console.log
async prepareDetailForSave(allModelSalesTargetDetail) {
const salesTargetDetailForSave: any = [];
for (const detail of allModelSalesTargetDetail) {
await new Promise ((resolve, reject) => {
if (detail.SeqNo === '') {
if (detail.Money !== 0) {
detail.RSeqNo = this.objMaster.SeqNo;
detail.CreateDate = UtilsService.getCurrentDate();
detail.ModifiedDate = UtilsService.getCurrentDate();
if (this.global.connectionStatus === 'offline') {
detail.SeqNo = UtilsService.getLocalSeqno().toString();
} else {
this.jamService.getInvoiceCode('sales-target-detail').subscribe((res: any) => {
detail.SeqNo = res['seqno'];
});
}
salesTargetDetailForSave.push(detail);
}
} else {
salesTargetDetailForSave.push(detail);
}
resolve();
});
}
return salesTargetDetailForSave;
}
I wrote this, but it looks rough, its for node js so all values need to be gotten in order
import tripfind from '../model/tripfind';
import addbook from '../model/addbook';
import bookfind from '../model/bookfind';
function addBook(info) {
return new Promise((resolve, reject) => {
let rowcount = -1;
bookfind(0, -1, -1).then((result) => {
if (result) {
rowcount = result.rowCount;
} else {
rowcount = 0;
}
tripfind(info.trip_id, 0).then((xresult) => {
let val = '';
if (xresult === 'false') {
val = 'trip is not valid';
resolve(val);
} else {
addbook(info, rowcount).then((value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
}).catch((err) => {
if (err) {
reject(err);
}
});
}
}).catch((error) => {
reject(error);
});
}).catch((err) => {
if (err) {
reject(err);
}
});
});
}
export default addBook;
thats the code above, the code also gets exported and is handled as a promise function, please help if you can
Without using async / await, this example is functionally equivalent to what you had before:
function addBook (info) {
return bookfind(0, -1, -1).then(result => {
const { rowCount } = result || { rowCount: 0 };
return tripfind(info.trip_id, 0).then(trip =>
trip === 'false'
? 'trip is not valid'
: addbook(info, rowCount)
);
});
}
Sections like
.then((value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
})
.catch((err) => {
if (err) {
reject(err);
}
});
.catch((error) => {
reject(error);
});
are completely superfluous, so removing them doesn't affect the logic at all. After removing those, and unwrapping your Promise constructor antipattern, you can see the logic becomes a lot more simplified.
You're missing some returns of promises, and if you like this way of writing you should indent then() and catch() in the same way. Also doing this
.catch((err) => {
if (err) {
reject(err);
}
});
is useless, caus you catch an error to reject it. Rewritten in promise pattern (and a bit of optimization):
function addBook(info) {
return bookfind(0, -1, -1)
.then((result) => {
rowcount = result ? result.rowCount : 0
return tripfind(info.trip_id, 0)
.then((xresult) => {
if (xresult === 'false') {
return 'trip is not valid'
}
return addbook(info, rowcount)
})
})
}
Anyway is more clear with the async/await pattern:
async function addBook(info) {
let result = await bookfind(0, -1, -1)
rowcount = result ? result.rowCount : 0
let xresult = await tripfind(info.trip_id, 0)
if (xresult === 'false')
return 'trip is not valid'
let value = await addbook(info, rowcount)
return value
}
like this?
function addBook(info) {
return bookfind(0, -1, -1).then((result) => tripfind(info.trip_id, 0)
.then((xresult) => (xresult === 'false') ?
'trip is not valid' :
addbook(info, result ? result.rowCount : 0)
)
);
}
or with async/await
async function addBook(info) {
const result = await bookfind(0, -1, -1);
const xresult = await tripfind(info.trip_id, 0)
return xresult === "false"?
'trip is not valid' :
addbook(info, result ? result.rowCount : 0);
}
Avoid the Promise/Deferred antipattern: What is the explicit promise construction antipattern and how do I avoid it?
and remove pointless/useless code, like this function for example:
(value) => {
if (value === 'invalid id') {
resolve('invalid id');
}
resolve(value);
}
which always resolves to value. that's like return value === 1? 1: value
Or all the (error) => { reject(error); } just to "forward" the error to the returned promise.
or this construct
let rowcount = -1;
//....
if (result) {
rowcount = result.rowCount;
} else {
rowcount = 0;
}
//....
doSomethingWith(rowcount)
which can be summed up as doSomethingWith(result ? result.rowCount : 0)
A lot of people might tell you that async/await will solve this better. I think the problem is more about how the promises are used. If you have access to change the functions you are calling here's what I'd recommend.
Have functions return things of the same shape - e.g., the call to bookfind can return a rejected promise or a resolved promise that wraps undefined or {..., :rowcount:12}. If we remove the undefined result and return a default {..., rowcount:0 } it would simplify the calling functions substantially and contain logic a bit better.
Remove the superfluous function calls (.catchs and the addbook .then bit)
Here's what those functions could look like (not knowing your setup at all)
function tripfind(id, num) {
return new Promise(function(resolve, reject) {
doSomething(function(err, results) {
if (err) return reject(err);
if (results === "false") return reject(new Error("trip is not valid"));
return resolve(results);
});
});
}
function bookfind(id, start, end /*or whatever*/) {
return new Promise(function(resolve, reject) {
findBooks(function(err, results) {
if (err) return reject(err);
// handle odd cases too
if (!results) return resolve({ rowcount: 0 });
return resolve(results);
});
});
}
and the usages
bookfind(0, -1, -1).then(results =>{}).catch(err=>{})
tripfind(id, 0).then(results =>{}).catch(err=>{})
Using Promises this way, we don't have to check the value of results because if it wasn't there, the promise shouldn't resolve. Likewise, the shape of the thing coming out of the promise should always be the same. (I call it a smell when a function returns String|Object|Undefined, it's possible but I'd look hard at it first)
Using this pattern and removing calls that don't do anything (all the .catch calls), we can simplify the code down to:
function addBook(info) {
return bookfind(0, -1, -1).then(({ rowcount }) =>
tripfind(info.trip_id, 0).then(() =>
addbook(info, rowcount)
)
);
}
I have the following function:
function JSON_to_buffer(json) {
let buff = Buffer.from(json);
if ( json.length < constants.min_draft_size_for_compression) {
return buff;
}
return zlib.deflate(buff, (err, buffer) => {
if (!err) {
return buffer;
} else {
return BPromise.reject(new VError({
name: 'BufferError',
}, err));
}
});
}
I want to be able to run this but have it wait if it goes to the unzip call. I'm currently calling this within a promise chain and it's going back to the chain without waiting for this and returns after the previous promise chain has completed.
You can put that logic into a Promise along with an async function
function JSON_to_buffer(json) {
return new Promise(function (resolve, reject) {
let buff = Buffer.from(json);
if (json.length < constants.min_draft_size_for_compression) {
return resolve(buff);
}
zlib.deflate(buff, (err, buffer) => {
if (!err) {
resolve(buffer);
} else {
reject(err);
/*return BPromise.reject(new VError({
name: 'BufferError',
}, ));*/
}
});
});
}
async function main() {
try {
let buffer = await JSON_to_buffer(myJSON);
} catch (e) {
console.log(e.message);
}
}
I want to stop promise chain after it resolved via some conditions. Below code is might useful to understand what am I saying.
function update(id, data) {
return new Promise((resolve, reject) => {
let conn;
pool.get()
.then((db) => {
conn = db;
if(Object.keys(data).length === 0) {
return resolve({ updated: 0 });
}
else {
return generateHash(data.password);
}
})
.then((hash) => {
conn.query("UPDATE ... ", (err, queryResult) => {
if(err) {
throw err;
}
resolve({ updated: queryResult.affectedRows });
});
})
.catch((err) => { ... })
});
}
Note that pool.get() is promise wrapped API for getting connection pool from MySQL module that I made.
What I'm trying to do is updating user data. And for save server resources, I avoided to update if no data to update(Object.keys(data).length === 0).
When I tried this code, second then(updating db) is always happening even if no data to update!
I read this post, but it didn't worked. Why the promise chain wasn't stopped when I called "return resolve();"? And how to I stop it properly? I really like using Promises, but sometimes, this kind of things make me crazy. It will be very appreciate to help me this problem. Thanks!
P.S. I'm using node v6.2.2 anyway.
Why the promise chain wasn't stopped when I called "return resolve();"?
You've returned from the current then callback and fulfilled the outer promise. But that doesn't "stop" anything, then then chain still will continue by resolving with the return value of the callback.
And how to I stop it properly?
You need to put the then call inside the if to have the condition apply to it:
pool.get()
.then((db) => {
…
if (Object.keys(data).length === 0) {
…({ updated: 0 });
} else {
return generateHash(data.password)
.then((hash) => {
conn.query("UPDATE ... ", (err, queryResult) => {
…
});
})
}
})
.catch((err) => { ... })
And in any case, you should avoid the Promise constructor antipattern! You should only promisify the query method:
function query(conn, cmd) {
return new Promise((resolve, reject) => {
conn.query(cmd, (err, queryResult) => {
if (err) reject(err); // Don't throw!
else resolve(queryResult);
});
});
}
and then use that:
function update(id, data) {
return pool.get()
.then(conn => {
if (Object.keys(data).length === 0) {
conn.close(); // ???
return { updated: 0 };
} else {
return generateHash(data.password)
.then(hash => {
return query(conn, "UPDATE ... ")
}).then(queryResult => {
conn.close(); // ???
return { updated: queryResult.affectedRows };
}, err => {
…
conn.close(); // ???
});
}
});
}
Notice that it might not make sense to get a connection from the pool if you can know beforehand that no query will be made, so probably you should put the if on the top level:
function update(id, data) {
if (Object.keys(data).length === 0) {
return Promise.resolve({ updated: 0 });
} else {
return pool.get()
.then(conn => {
return generateHash(data.password)
.then(hash => {
return query(conn, "UPDATE ... ")
}).then(queryResult => {
conn.close(); // ???
return { updated: queryResult.affectedRows };
}, err => {
…
conn.close(); // ???
});
});
}
}
This would be a good situation to use an if statement:
function update(id, data) {
if (Object.keys(data).length === 0) {
return Promise.resolve({ updated: 0 });
}
let conn;
return pool.get()
.then((db) => {
conn = db;
return generateHash(data.password);
})
.then((hash) => {
return new Promise(function (resolve, reject) {
conn.query("UPDATE ... ", (err, queryResult) => {
if(err) {
reject(err);
}
resolve({ updated: queryResult.affectedRows });
});
});
})
.catch((err) => { ... })
}