NB: this javascript uses the openui5 libraries
attachOnceAsync: function(binding, callback) {
var args = [...arguments]
args.splice(0, 2)
return new Promise((resolve, reject) => {
var dataReceived = function(oEvent) {
if (typeof callback == 'function') {
if (callback.constructor.name === "AsyncFunction") {
callback(oEvent, ...args).then(() =>
resolve(oEvent)
).catch((err) => {
reject(err)
})
} else {
try {
callback(oEvent, ...args);
resolve(oEvent)
} catch (err) {
reject(err)
}
}
} else {
resolve(oEvent, ...args);
}
}
binding.attachEventOnce("dataReceived", dataReceived);
})
}
There aren't any Promise versions of events like "dataReceived" so this is my attempt to wrap one up.
In this case, callback is undefined (it shouldn't be, but that's a different issue). The line resolve(oEvent, ...args); is hit instead, only it never returns out of the await!
I changed that line to just resolve(oEvent) but still no joy.
Any ideas what I'm doing wrong?
Thanks
PS: I'll keep an eye on this question so I can offer any extra info required
here's how I call it:
handleAsync: async function(controller) {
/* Lots of setup */
var response = await fetch(/*redacted*/ )
if (response.status === 400) {
console.log(await response.json());
throw new BadRequestException();
}
if (response.status === 204){
/* more setup */
context = oPanel.getBindingContext("odata");
var updatePurchaseOrderDocTotalFunc = this.updatePurchaseOrderDocTotal
var callback = async function() {
var docTotalUpdate = await updatePurchaseOrderDocTotalFunc(oView, context, oModel, agreedPrice);
if (docTotalUpdate && docTotalUpdate.status === 204) {
await context.refresh();
}
}
//-- HERE --
await AsyncBinding.attachOnceAsync(binding,callback)
await context.refresh();
}
return true;
}
Ok - The answer was staring at me.
Sorry I wasn't able to provide a working snippet but the ui5 scripts are quite extensive.
I have several other functions that return a Promise so I can use the await/async approach, but with this one I forgot to include the action that causes the event to fire.
I renamed the function to show what it now does, and included the action that causes the event to fire:
RefreshAsync : function(context,binding, callback){
var args = [...arguments]
args.splice(0,2)
return new Promise((resolve,reject)=>{
var dataReceived = function(oEvent){
if(typeof callback == 'function' ){
if(callback.constructor.name==="AsyncFunction"){
callback(oEvent, ...args).then(()=>{
resolve(oEvent)
}).catch((err)=>{
reject(err)
})
}else{
try{
callback(oEvent, ...args);
resolve(oEvent)
}catch(err){
reject(err)
}
}
}else{
resolve([oEvent, ..args])
}
}
binding.attachEventOnce("dataReceived", dataReceived);
//This is the action I missed
context.Refresh()
})
},
Related
I have my function whose job is to go over a number of files (that use the values from the array as building blocks for file names) and download them using a reduce. It's more of a hack as of now but the Promise logic should work. Except it doesn.t
Here's my code:
function import_demo_files(data) {
/**
* Make a local copy of the passed data.
*/
let request_data = $.extend({}, data);
const get_number_of_files_1 = Promise.resolve({
'data' : {
'number_of_files' : 2
}
});
return new Promise((resolve, reject) => {
let import_files = get_number_of_files_1.then(function(response) {
new Array(response.data.number_of_files).fill(request_data.step_name).reduce((previous_promise, next_step_identifier) => {
let file_counter = 1;
return previous_promise.then((response) => {
if( response !== undefined ) {
if('finished_import' in response.data && response.data.finished_import === true || response.success === false) {
return import_files;
}
}
const recursively_install_step_file = () => import_demo_file({
demo_handle: request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': file_counter
}).call().then(function(response) {
file_counter++;
if('file_counter' in response.data && 'needs_resume' in response.data) {
if(response.data.needs_resume === true) {
file_counter = response.data.file_counter;
}
}
return response.data.keep_importing_more_files === true ? recursively_install_step_file() : response
});
return recursively_install_step_file();
}).catch(function(error) {
reject(error);
});
}, Promise.resolve())
}).catch(function(error) {
reject(error);
});
resolve(import_files);
});
}
Now, when I do:
const import_call = import_demo_files({ 'demo_handle' : 'demo-2', 'step_name' : 'post' });
console.log(import_call);
The console.log gives me back that import_call is, in fact a promise and it's resolved. I very much like the way return allows me to bail out of a promise-chain, but I have no idea how to properly resolve my promise chain in there, so clearly, it's marked as resolved when it isn't.
I would like to do import_call.then(... but that doesn't work as of now, it executes this code in here before it's actually done because of the improper handling in import_demo_files.
An asynchronous recursion inside a reduction isn't the simplest of things to cut your teeth on, and it's not immediately obvious why you would want to given that each iteration of the recursion is identical to every other iteration.
The reduce/recurse pattern is simpler to understand with the following pulled out, as outer members :
1. the `recursively_install_step_file()` function
1. the `new Array(...).fill(...)`, as `starterArray`
1. the object passed repeatedly to `import_demo_file()`, as `importOptions`)
This approach obviates the need for the variable file_counter, since importOptions.file_counter can be updated directly.
function import_demo_files(data) {
// outer members
let request_data = $.extend({}, data);
const importOptions = {
'demo_handle': request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': 1
};
const starterArray = new Array(2).fill(request_data.step_name);
function recursively_install_step_file() {
return import_demo_file(importOptions).then((res) => {
if('file_counter' in res.data && 'needs_resume' in res.data && res.data.needs_resume) {
importOptions.file_counter = res.data.file_counter; // should = be += ?
} else {
importOptions.file_counter++;
}
return res.data.keep_importing_more_files ? recursively_install_step_file() : res;
});
}
// the reduce/recurse pattern
return starterArray.reduce((previous_promise, next_step_identifier) => { // next_step_identifier is not used?
let importOptions.file_counter = 1; // reset to 1 at each stage of the reduction?
return previous_promise.then(response => {
if(response && ('finished_import' in response.data && response.data.finished_import || !response.success)) {
return response;
} else {
return recursively_install_step_file(); // execution will drop through to here on first iteration of the reduction
}
});
}, Promise.resolve());
}
May not be 100% correct but the overall pattern should be about right. Be prepared to work on it some.
I'm working on a project in Node.js that uses user certificates. I need to generate them synchronously, in a blocking manner, but the library I use (pem) has only asynchronous functions (callbacks). I tried multiple ways to tackle the problem, but none of my tries have been successful. My code looks like this:
function KeyObject(CN, serverKey, days = 365) { // key object
if (typeof CN !== 'string' ||
typeof days !== 'number' ||
typeof serverKey !== 'object') {
throw TypeError;
}
this.CN = CN;
this.days = days;
const _this = this;
async function generatePrivate() {
var p = new Promise((resolve, reject) => {
pem.createPrivateKey((err, obj) => {
if (err) reject(err);
_this.private = obj.key;
resolve();
});
});
await p;
}
async function generateCert(serviceKey) {
if (typeof serviceKey !== 'object') {
throw TypeError;
}
var p = new Promise((resolve, reject) => {
pem.createCertificate({
commonName: _this.CN,
days: _this.days,
serviceKey: serviceKey.private
}, (err, obj) => {
if (err) reject(err);
_this.cert = obj.certificate;
resolve();
});
});
await p;
}
// init the keys
generatePrivate();
generateCert(serverKey);
}
This code goes straight through and doesn't wait for the functions to complete. What should I do?
Thanks in advance.
You should just return the Promise from each of those functions - no need to await or make them async. You also can't have an async constructor, perhaps adding an async factory would work.
I'm trying to wrap http.get with a Promise. Here's what I've got:
import Http from 'http';
export function get(url) {
let req;
return new Promise((resolve, reject) => {
req = Http.get(url, async res => {
if(res.statusCode !== 200) {
return reject(new Error(`Request failed, got status ${res.statusCode}`));
}
let contentLengthStr = res.headers['content-length'];
if(contentLengthStr) {
let contentLength = parseInt(contentLengthStr, 10);
if(contentLength >= 0 && contentLength <= 2*1024**3) {
let buf = Buffer.allocUnsafe(contentLength);
let offset = 0;
res.on('data', chunk => {
if(chunk.length + offset > contentLength) {
return reject(new Error(`Received too much data, expected ${contentLength} bytes`));
}
chunk.copy(buf, offset);
offset += chunk.length;
});
res.on('end', () => {
if(offset === contentLength) {
resolve(buf);
} else {
return reject(new Error(`Expected ${contentLength} bytes, received ${offset}`));
}
})
} else {
return reject(new Error(`Bad Content-Length header: ${contentLengthStr}`));
}
} else {
return reject(new Error(`Missing Content-Length header not supported`));
}
});
}).catch(err => {
req.abort();
throw err;
})
}
It seems to work OK, but it feels a bit clunky.
Firstly, async/await appear to be of no help here. I can't throw nor return inside of res.on('end'. Returning just returns out of the end callback, but there's no real way for me to break out of the Http.get(url, res => { function from inside there. throwing doesn't "bubble up" to the Promise I created either because the data/end events don't fire synchronously. I have to call reject manually.
The part that really bothers me though is that if the server sends me more data than they said they were going to send me via the Content-Length header, I want to abort the request and stop listening for events. To do that I've rejected the Promise, and then immediately catch it so that I can abort the request, and then I rethrow the error so that the caller can handler it. To do this I have to declare the req variable above the Promise, and then initialise it inside the Promise, so that I can access it after the Promise.
The flow of everything just feels really clunky. Is there a nicer way to write this? (Assume I have available all ES6/ES2017+/next features)
Here is how I would approach the problem:
"promisify' Http.get, meaning converting the API to use promises.
Separate the stream logic into its own separate promise.
Instrument the error logic in a clear flat async function.
This looks something like the following:
import Http from 'http';
// promisify Http.get
const http = (url, abort) => new Promise((resolve, reject) => {
let req = Http.get(url, res => {
if(res.statusCode !== 200) reject(new Error(`Request failed, got status ${res.statusCode}`));
else resolve(res);
});
abort(() => req.abort()); // allow aborting via passed in function
});
export async function get(url) {
let abort = null;
let res = await http(url, (onabort) => { abort = onabort; });
let contentLengthStr = res.headers['content-length'];
if(!contentLengthStr) new Error(`Missing Content-Length header not supported`);
if(contentLengthStr < 0 || contentLengthStr > (2*1024**3 +1)) {
throw new Error(`Bad Content-Length header: ${contentLengthStr}`);
}
try {
return await read(res, contentLength);
} catch (e) {
abort();
throw e;
}
}
function read(res, contentLength) {
let buf = Buffer.allocUnsafe(contentLength), offset = 0;
return new Promise((resolve, reject) => {
res.on('data', chunk => {
if(chunk.length + offset > contentLength) {
return reject(new Error(`Received too much data, expected ${contentLength} bytes`));
}
chunk.copy(buf, offset);
offset += chunk.length;
});
res.on('end', () => {
if(offset === contentLength) resolve(buf);
else return reject(new Error(`Expected ${contentLength} bytes, received ${offset}`));
});
});
}
Technically this should be on code review, but it's a different audience.
First observation. Correct me if I am wrong, but HTTP.get does not document usage of a return value from the call back. So unless function code uses the await keyword, leaving the callback defined with the async keyword is confusing (you did mention it was of no help).
A second source of confusion for readers is usage of the return reject( ...... ); construct. reject doesn't return a value, and I am unaware of the return value of event listeners being used. So the the order can be reversed by placing the return statement (if actually needed) after the reject call.
Aborting the request can be done in scope of the promise executor by moving let req; back into the executor and explicitly calling .abort as required. Catching the promise should no longer be required. Refactored, the event emitter callbacks could, for example, look like
res.on('data', chunk => {
if(chunk.length + offset > contentLength) {
reject(new Error(`Received too much data, expected ${contentLength} bytes`));
req.abort();
} else {
chunk.copy(buf, offset);
offset += chunk.length;
}
});
res.on('end', () => {
if(offset === contentLength) {
resolve(buf);
} else {
reject( new Error(`Expected ${contentLength} bytes, received ${offset}`));
req.abort();
}
});
Which just leaves the last two return reject.... usages. You could either abort the request in each case, or set an error variable which is checked later:
req = Http.get(url, res => {
let error = null;
// ....
} else {
error = new Error(`Bad Content-Length header: ${contentLengthStr}`);
}
} else {
error = new Error(`Missing Content-Length header not supported`));
}
if( error) {
reject( error);
req.abort();
}
});
At this stage catching promise rejection and re-throwing the error should no longer be needed. Naturally this is untested but hopefully provides some useful feedback.
I have an issue where I may need to jump out of the whole promise chain because of some value or take two paths based on a value.
How best do you do that?
Here is the first scenario where I would like to just jump out of the whole chain. I just want to give them a message.
DB_WorkIssues.info().then(function (details) {
if (details.doc_count == 0 && details.update_seq == 0) {
showMsg("This device has no local data on it and no access to the Server, please come back when you are online.")
} jump out here, no need to do the next set.
else
return; Continue on as the values are valid.
}).then(function () {
return ajaxCallForJson(URI_LookupTables);
}).then(function (json) {
return callBulkDocLoad(DB_LookupTables, json);
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
In the 2nd scenario, I may want to take one path if the values are one thing and another if the values are another. But I still want the chains to work with the first promise. Something like this:
DB_WorkIssues.info().then(function (details) {
if (details.doc_count == 0 && details.update_seq == 0) {
Take this path.
return;
}).then(function () {
return ajaxCallForJson(URI_LookupTables);
}).then(function (json) {
return callBulkDocLoad(DB_LookupTables, json);
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
}
else
{
Take this path instead
return;
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
}
Thanks.
Here is what I was thinking after looking at the answer where I do the second promise always and only do the first in some cases.
DB_WorkIssues.info().then(function(details) {
// promise variable , defined in conditional
var promise;
Would I set the promise to some default value, in case the following test fails
if (details.doc_count == 0 && details.update_seq == 0) {
// return this promise
promise = ajaxCallForJson(URI_LookupTables).then(function(json) {
return callBulkDocLoad(DB_LookupTables, json);
});
}
return promise;
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).then(function () {
return loadLocationsDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
Is that how I could do it?
Thanks.
I think this is a skeleton that represents what you're going for. Promises are incredibly powerful and worth studying. I tried to add helpful comments but I suggest playing around with the code and understanding what's going on.
// Six named promise-creators. When called with (x), will create a promise
// which waits 200ms and then logs and resolves with (x).
// These could represent any asynchronous operation.
const p1 = p2 = p3 = p4 = p5 = p6 =
(x) => {
const p = new Promise((resolve, reject) => {
setTimeout(() => {resolve(x); console.log(x)}, 200)
});
return p;
}
// A function which, when called, will execute first promise chain.
const first_steps = () =>
p1(1)
.then(result => p2(2))
.then(result => p3(3))
// A function which, when called, will execute second promise chain.
const second_steps = () =>
p4(4)
.then(result => p5(5))
.then(result => p6(6))
// When true, this prints numbers 1-6.
// When false, only prints numbers 4-6.
if (false) {
console.log(first_steps().then(second_steps));
} else {
second_steps();
}
Seems to me you have extra sets of then() and your if() would determine which promise to return something like:
DB_WorkIssues.info().then(function(details) {
// promise variable , defined in conditional
var promise;
if (details.doc_count == 0 && details.update_seq == 0) {
// return this promise
promise = ajaxCallForJson(URI_LookupTables).then(function(json) {
return callBulkDocLoad(DB_LookupTables, json);
});
} else {
// or this promise
promise = loadCategoriesDDL().then(function() {
return loadEquipmentDDL();
});
}
promise.catch(function(err) {
showMsg("Error in defineDBs: " + err);
});
return promise;
})
I a promise in such fashion,
function getMode(){
var deferred = Promise.defer();
checkIf('A')
.then(function(bool){
if(bool){
deferred.resolve('A');
}else{
return checkIf('B');
}
}).then(function(bool){
if(bool){
deferred.resolve('B');
}else{
return checkIf('C');
}
}).then(function(bool){
if(bool){
deferred.resolve('C');
}else{
deferred.reject();
}
});
return deferred.promise;
}
checkIf returns a promise, and yes checkIf cannot be modified.
How do I break out of the chain at the first match? (any way other than explicitly throwing error?)
Any way other than explicitly throwing error?
You may need to throw something, but it does not have to be an error.
Most promise implementations have method catch accepting the first argument as error type (but not all, and not ES6 promise), it would be helpful under this situation:
function BreakSignal() { }
getPromise()
.then(function () {
throw new BreakSignal();
})
.then(function () {
// Something to skip.
})
.catch(BreakSignal, function () { })
.then(function () {
// Continue with other works.
});
I add the ability to break in the recent implementation of my own promise library. And if you were using ThenFail (as you would probably not), you can write something like this:
getPromise()
.then(function () {
Promise.break;
})
.then(function () {
// Something to skip.
})
.enclose()
.then(function () {
// Continue with other works.
});
You can use
return { then: function() {} };
.then(function(bool){
if(bool){
deferred.resolve('A');
return { then: function() {} }; // end/break the chain
}else{
return checkIf('B');
}
})
The return statement returns a "then-able", only that the then method does nothing.
When returned from a function in then(), the then() will try to get the result from the thenable.
The then-able's "then" takes a callback but that will never be called in this case. So the "then()" returns, and the callback for the rest of the chain does not happen.
I think you don't want a chain here. In a synchronous fashion, you'd have written
function getMode(){
if (checkIf('A')) {
return 'A';
} else {
if (checkIf('B')) {
return 'B';
} else {
if (checkIf('C')) {
return 'C';
} else {
throw new Error();
}
}
}
}
and this is how it should be translated to promises:
function getMode(){
checkIf('A').then(function(bool) {
if (bool)
return 'A';
return checkIf('B').then(function(bool) {
if (bool)
return 'B';
return checkIf('C').then(function(bool) {
if (bool)
return 'C';
throw new Error();
});
});
});
}
There is no if else-flattening in promises.
I would just use coroutines/spawns, this leads to much simpler code:
function* getMode(){
if(yield checkIf('A'))
return 'A';
if(yield checkIf('B'))
return 'B';
if(yield checkIf('C'))
return 'C';
throw undefined; // don't actually throw or reject with non `Error`s in production
}
If you don't have generators then there's always traceur or 6to5.
You could create a firstSucceeding function that would either return the value of the first succeeded operation or throw a NonSucceedingError.
I've used ES6 promises, but you can adapt the algorithm to support the promise interface of your choice.
function checkIf(val) {
console.log('checkIf called with', val);
return new Promise(function (resolve, reject) {
setTimeout(resolve.bind(null, [val, val === 'B']), 0);
});
}
var firstSucceeding = (function () {
return function (alternatives, succeeded) {
var failedPromise = Promise.reject(NoneSucceededError());
return (alternatives || []).reduce(function (promise, alternative) {
return promise.then(function (result) {
if (succeeded(result)) return result;
else return alternative();
}, alternative);
}, failedPromise).then(function (result) {
if (!succeeded(result)) throw NoneSucceededError();
return result;
});
}
function NoneSucceededError() {
var error = new Error('None succeeded');
error.name = 'NoneSucceededError';
return error;
}
})();
function getMode() {
return firstSucceeding([
checkIf.bind(null, 'A'),
checkIf.bind(null, 'B'),
checkIf.bind(null, 'C')
], function (result) {
return result[1] === true;
});
}
getMode().then(function (result) {
console.log('res', result);
}, function (err) { console.log('err', err); });
i like a lot of the answers posted so far that mitigate what the q readme calls the "pyramid of doom". for the sake of discussion, i'll add the pattern that i plunked out before searching around to see what other people are doing. i wrote a function like
var null_wrap = function (fn) {
return function () {
var i;
for (i = 0; i < arguments.length; i += 1) {
if (arguments[i] === null) {
return null;
}
}
return fn.apply(null, arguments);
};
};
and i did something totally analogous to #vilicvane's answer, except rather than throw new BreakSignal(), i'd written return null, and wrapped all subsequent .then callbacks in null_wrap like
then(null_wrap(function (res) { /* do things */ }))
i think this is a good answer b/c it avoids lots of indentation and b/c the OP specifically asked for a solution that doesn't throw. that said, i may go back and use something more like what #vilicvane did b/c some library's promises might return null to indicate something other than "break the chain", and that could be confusing.
this is more a call for more comments/answers than a "this is definitely the way to do it" answer.
Probably coming late the party here, but I recently posted an answer using generators and the co library that would answer this question (see solution 2):
https://stackoverflow.com/a/43166487/1337392
The code would be something like:
const requestHandler = function*() {
const survey = yield Survey.findOne({
_id: "bananasId"
});
if (survey !== null) {
console.log("use HTTP PUT instead!");
return;
}
try {
//saving empty object for demonstration purposes
yield(new Survey({}).save());
console.log("Saved Successfully !");
return;
}
catch (error) {
console.log(`Failed to save with error: ${error}`);
return;
}
};
co(requestHandler)
.then(() => {
console.log("finished!");
})
.catch(console.log);
You would pretty much write synchronous code that would be in reality asynchronous !
Hope it helps!
Try to use libs like thisone:
https://www.npmjs.com/package/promise-chain-break
db.getData()
.then(pb((data) => {
if (!data.someCheck()) {
tellSomeone();
// All other '.then' calls will be skiped
return pb.BREAK;
}
}))
.then(pb(() => {
}))
.then(pb(() => {
}))
.catch((error) => {
console.error(error);
});