How can I dispose the serial when the promise is rejected?
The dispose function prints error for rejected promises.
Unhandled rejection TypeError: Cannot call method 'isOpen' of
undefined
var pingPort = function(port){
return new promise(function(resolve, reject){
var serial = new com.SerialPort(port.comName, {
baudrate: 19200,
parser: com.parsers.readline(lineEnd)
}, false);
serial.on("data", function(data){
if (data === responseUuid){
resolve(serial);
}
});
serial.open(function(err){
if (err){
//reject(serial)
}
else{
serial.write(pingUuid + lineEnd);
}
});
});
};
var dispose = function(port){
console.log(port.isOpen());
};
var findPort = function(){
com.listAsync().map(function(port){
return pingPort(port).timeout(100).catch(promise.TimeoutError, function(err) {
console.log("Ping timout: " + port.comName);
dispose(port);
}).catch(function(err){
console.log("Ping error: " + port.comName);
dispose(port);
});
}).each(dispose);
}();
This question almost answers itself, in that "promise" and "dispose" together beg you to consider the Promise Disposer Pattern, which is actually nothing more than judicious use of .then() or .finally() with a callback that disposes/closes something that was created/opened earlier.
With that pattern in mind, you will find it simpler to orchestrate disposal in pingPort() rather than findPort().
Assuming the code in the question to be broadly correct, you can chain finally() as follows :
var pingPort = function(port) {
var serial; // outer var, necessary to make `serial` available to .finally().
return new promise(function(resolve, reject) {
serial = new com.SerialPort(port.comName, {
baudrate: 19200,
parser: com.parsers.readline(lineEnd)
}, false);
serial.on('data', function(data) {
if(data === responseUuid) {
resolve(serial); //or resolve(port);
}
});
serial.open(function(err) {
if(err) {
reject('failed to open');
} else {
serial.write(pingUuid + lineEnd);
}
});
})
.finally(function() { // <<<<<<<<<<<
serial.close(); // "dispose".
});
};
Unlike .then, .finally's handler doesn't modify the value/reason, so can be placed in mid-chain without the need to worry about returning a value or rethrowing an error. This point is rather poorly made in the Bluebird documentation.
With disposal in pingPort(), findPort() will simplify as follows :
var findPort = function(){
com.listAsync().map(function(port) {
return pingPort(port).timeout(100)
.catch(promise.TimeoutError, function(err) {
console.log("Ping timout: " + port.comName);
})
.catch(function(err){
console.log("Ping error: " + port.comName);
});
});
}();
Going further, you may want to do more with the array of promises returned by .map(), but that's another issue.
Related
I'm writing a simple app which use nativescript-geolocation API.
The function getCurrentLocation basically works fine, but when I moved to another file named maps-module.js and call it from main thread from file detail.js, the object location it return is NULL.
After print to console the object, I realized that the variable returned_location was returned before the function finish finding location.
I think its the multi-thread problems but I really don't know how to fix it.
Here are my files.
detail.js
var Frame = require("ui/frame");
var Observable = require("data/observable");
var MapsModel = require("../../view-models/maps-model");
var defaultMapInfo = new MapsModel({
latitude: "10.7743332",
longitude: "106.6345204",
zoom: "0",
bearing: "0",
tilt: "0",
padding: "0"
});
var page;
var mapView;
exports.pageLoaded = function(args) {
page = args.object;
var data = page.navigationContext;
page.bindingContext = defaultMapInfo;
}
exports.onBackTap = function () {
console.log("Back to home");
var topmost = Frame.topmost();
topmost.goBack();
}
function onMapReady(args) {
mapView = args.object;
mapView.settings.zoomGesturesEnabled = true;
}
function onMarkerSelect(args) {
console.log("Clicked on " + args.marker.title);
}
function onCameraChanged(args) {
console.log("Camera changed: " + JSON.stringify(args.camera));
}
function getCurPos(args) {
var returned_location = defaultMapInfo.getCurrentPosition(); // variable is returned before function finished
console.dir(returned_location);
}
exports.onMapReady = onMapReady;
exports.onMarkerSelect = onMarkerSelect;
exports.onCameraChanged = onCameraChanged;
exports.getCurPos = getCurPos;
maps-module.js
var Observable = require("data/observable");
var Geolocation = require("nativescript-geolocation");
var Gmap = require("nativescript-google-maps-sdk");
function Map(info) {
info = info || {};
var _currentPosition;
var viewModel = new Observable.fromObject({
latitude: info.latitude || "",
longitude: info.longitude || "",
zoom: info.zoom || "",
bearing: info.bearing || "",
tilt: info.bearing || "",
padding: info.padding || "",
});
viewModel.getCurrentPosition = function() {
if (!Geolocation.isEnabled()) {
Geolocation.enableLocationRequest();
}
if (Geolocation.isEnabled()) {
var location = Geolocation.getCurrentLocation({
desiredAccuracy: 3,
updateDistance: 10,
maximumAge: 20000,
timeout: 20000
})
.then(function(loc) {
if (loc) {
console.log("Current location is: " + loc["latitude"] + ", " + loc["longitude"]);
return Gmap.Position.positionFromLatLng(loc["latitude"], loc["longitude"]);
}
}, function(e){
console.log("Error: " + e.message);
});
if (location)
console.dir(location);
}
}
return viewModel;
}
module.exports = Map;
If Shiva Prasad's footnote ...
"geolocation.enableLocationRequest() is also an asynchorous method"
... is correct, then the Promise returned by geolocation.enableLocationRequest() must be handled appropriately and the code will change quite considerably.
Try this :
viewModel.getCurrentPosition = function(options) {
var settings = Object.assign({
'desiredAccuracy': 3,
'updateDistance': 10,
'maximumAge': 20000,
'timeout': 20000
}, options || {});
var p = Promise.resolve() // Start promise chain with a resolved native Promise.
.then(function() {
if (!Geolocation.isEnabled()) {
return Geolocation.enableLocationRequest(); // return a Promise
} else {
// No need to return anything here.
// `undefined` will suffice at next step in the chain.
}
})
.then(function() {
if (Geolocation.isEnabled()) {
return Geolocation.getCurrentLocation(settings); // return a Promise
} else { // <<< necessary to handle case where Geolocation didn't enable.
throw new Error('Geolocation could not be enabled');
}
})
.then(function(loc) {
if (loc) {
console.log("Current location is: " + loc.latitude + ", " + loc.longitude);
return Gmap.Position.positionFromLatLng(loc.latitude, loc.longitude);
} else { // <<< necessary to handle case where loc was not derived.
throw new Error('Geolocation enabled, but failed to derive current location');
}
})
.catch(function(e) {
console.error(e);
throw e; // Rethrow the error otherwise it is considered caught and the promise chain will continue down its success path.
// Alternatively, return a manually-coded default `loc` object.
});
// Now race `p` against a timeout in case enableLocationRequest() hangs.
return Promise.race(p, new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error('viewModel.getCurrentPosition() timed out'));
}, settings.timeout);
}));
}
return viewModel;
Notes :
Starting the chain with a resolved native Promise gives much the same effect as wrapping in new Promise(...) but is cleaner chiefly because unexpected throws within the chain are guaranteed to deliver an Error object down the chain's error path without needing to try/catch/reject(). Also, in the two lines labelled "return a Promise", we don't need to care whether we return a Promise or a value; either will be assimilated by the native Promise chain.
Two else clauses are included to cater for failure cases which would not automatically throw.
The Promise.race() shouldn't be necessary but is a safeguard against the issue reported here. It's possible that the inbuilt 'timeout' mechanism will suffice. This extra timeout mechanism is a "belt-and-braces" measure.
A mechanism is included to override the hard-coded defaults in viewModel.getCurrentPosition by passing an options object. To run with the defaults, simply call viewModel.getCurrentPosition(). This feature was introduced primarily to allow settings.timeout to be reused in the Promise.race().
EDIT:
Thanks #grantwparks for the information that Geolocation.isEnabled() also returns Promise.
So now we can start the Promise chain with p = Geolocation.isEnabled().... and test the asynchronously delivered Boolean. If false then attempt to enable.
From that point on, the Promise chain's success path will be followed if geolocation was initially enabled or if it has been enabled. Further tests for geolocation being enabled disappear.
This should work:
viewModel.getCurrentPosition = function(options) {
var settings = Object.assign({
'desiredAccuracy': 3,
'updateDistance': 10,
'maximumAge': 20000,
'timeout': 20000
}, options || {});
var p = Geolocation.isEnabled() // returned Promise resolves to true|false.
.then(function(isEnabled) {
if (isEnabled) {
// No need to return anything here.
// `undefined` will suffice at next step in the chain.
} else {
return Geolocation.enableLocationRequest(); // returned Promise will cause main chain to follow success path if Geolocation.enableLocationRequest() was successful, or error path if it failed;
}
})
.then(function() {
return Geolocation.getCurrentLocation(settings); // return Promise
})
.then(function(loc) {
if (loc) {
console.log("Current location is: " + loc.latitude + ", " + loc.longitude);
return Gmap.Position.positionFromLatLng(loc.latitude, loc.longitude);
} else { // <<< necessary to handle case where loc was not derived.
throw new Error('Geolocation enabled, but failed to derive current location');
}
})
.catch(function(e) {
console.error(e);
throw e; // Rethrow the error otherwise it is considered caught and the promise chain will continue down its success path.
// Alternatively, return a manually-coded default `loc` object.
});
// Now race `p` against a timeout in case Geolocation.isEnabled() or Geolocation.enableLocationRequest() hangs.
return Promise.race(p, new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error('viewModel.getCurrentPosition() timed out'));
}, settings.timeout);
}));
}
return viewModel;
Since getting location is an Async process, your viewModel.getCurrentPosition should return a promise, and would look something like this,
viewModel.getCurrentPosition() {
return new Promise((resolve, reject) => {
geolocation
.getCurrentLocation({
desiredAccuracy: enums.Accuracy.high,
updateDistance: 0.1,
maximumAge: 5000,
timeout: 20000
})
.then(r => {
resolve(r);
})
.catch(e => {
reject(e);
});
});
}
and then when you use it, it would look like this
defaultMapInfo.getCurrentPosition()
.then(latlng => {
// do something with latlng {latitude: 12.34, longitude: 56.78}
}.catch(error => {
// couldn't get location
}
}
Hope that helps :)
Update: BTW, geolocation.enableLocationRequest() is also an asynchorous method.
New to promises, trying to understand the logic flow. Thought I understood until I started to insert errors to test. In the example below, when I comment out the 3rd line, why doesn't Reject get returned in the promise?
var Q = require( "q" )
var getInstallBase = function() {
return new Promise(function(resolve, reject) {
//var IBdata = 'temp IBdata'; // <------comment out so IBdata not defined
if (IBdata){
resolve(IBdata); // State will be fulfilled
} else {
reject("error getting IBdata"); // State will be rejected
}
});
}
var mungeIt = function(IBdata){
return new Q.Promise(function(resolve,reject){
// get insight from data
console.log('IBdata = ' + IBdata);
var insight = 'temp insight';
if (insight){
resolve(insight); // State will be fulfilled
} else {
reject("error getting insight"); // State will be rejected
}
})
}
var postResults = function(insight) {
return new Promise(function(resolve, reject) {
console.log('insight = ' + insight);
// post result
var objectID = '12345';
if (objectID){
setTimeout(function() {
console.log('done waiting');
resolve(objectID);
}, 2000);
// State will be fulfilled
} else {
reject("error posting insight to object store"); // State will be rejected
}
});
};
(function extractInsightCycle() {
getInstallBase().then(mungeIt).then(postResults).then(function(objectID) {
console.log('object successfully posted, ID: ' + objectID)
extractInsightCycle();
}).catch(function(error) {
console.log('something went wrong', error);
extractInsightCycle();
})
} )();
Please check out this JSBin: http://jsbin.com/zutotuvomo/edit?js,console
It contains a simplified version of your code, and as you can see, the promise returned from getInstallBase indeed rejects.
Probably something else in your code makes it look like it did not reject.
EDIT:
I took another look at the code, and this is what happens - the rejected promise is being caught by:
function(error) {
console.log('something went wrong', error);
extractInsightCycle();
}
Which calls the extractInsightCycle which is the function that's initiating the whole process, and this causes an infinite loop (endless recursion actually).
I have a small problem, this script works perfectly, with one problem, the "runTenant" method is not returning a promise (that needs resolving from "all()".
This code:
Promise.resolve(runTenant(latest)).then(function() {
end();
});
Calls this code:
function runTenant(cb) {
return new Promise(function() {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
new Tenant().fetchAll()
.then(function(tenants) {
if (tenants.models.length == 0) {
return;
} else {
async.eachSeries(tenants.models, function(tenant, next) {
var account = tenant.attributes;
Promise.resolve(db_tenant.config(account)).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
Promise.resolve(knex_pending).then(function() {
next(null, null);
});
} else {
next(null, null);
}
});
});
};
});
});
}
The code from runTenant is working correctly however it stalls and does not proceed to "end()" because the promise from "runTenant(latest)" isn't being resolved.
As if it weren't apparent, I am horrible at promises. Still working on getting my head around them.
Many thanks for any help/direction!
You should not use the Promise constructor at all here (and basically, not anywhere else either), even if you made it work it would be an antipattern. You've never resolved that promise - notice that the resolve argument to the Promise constructor callback is a very different function than Promise.resolve.
And you should not use the async library if you have a powerful promise library like Bluebird at hand.
As if it weren't apparent, I am horrible at promises.
Maybe you'll want to have a look at my rules of thumb for writing promise functions.
Here's what your function should look like:
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll().then(function(tenants) {
// if (tenants.models.length == 0) {
// return;
// } else
// In case there are no models, the loop iterates zero times, which makes no difference
return Promise.each(tenants.models, function(tenant) {
var account = tenant.attributes;
return db_tenant.config(account).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
return cb(knex_tenant); // can return a promise
}
});
});
});
}
Your promise in runTenant function is never resolved. You must call resolve or reject function to resolve promise:
function runTenant() {
return new Promise(function(resolve, reject) {
// somewhere in your code
if (err) {
reject(err);
} else {
resolve();
}
});
});
And you shouldn't pass cb in runTenant function, use promises chain:
runTenant()
.then(latest)
.then(end)
.catch(function(err) {
console.log(err);
});
You need to return all the nested promises. I can't run this code, so this isn't a drop it fix. But hopefully, it helps you understand what is missing.
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll() //added return
.then(function (tenants) {
if (tenants.models.length == 0) {
return;
} else {
var promises = []; //got to collect the promises
tenants.models.each(function (tenant, next) {
var account = tenant.attributes;
var promise = Promise.resolve(db_tenant.config(account)).then(function (knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
return knex_pending; //return value that you want the whole chain to resolve to
}
});
promises.push(promise); //add promise to collection
});
return Promise.all(promises); //make promise from all promises
}
});
}
I have an app using IndexedDB. Originally I made prolific use of callbacks, and decided to clean it up using angularjs promises $q.
FAIL.
http://jsfiddle.net/ed4becky/bumm337e/
angular.module("IDBTest", []);
angular.module("IDBTest")
.service("initSvc", ['$q', function ($q) {
var svc = this;
svc.dbVersion = 1;
svc.open = open;
svc.deleteDB = deleteDB;
var idb = window.indexedDB;
function deleteDB() {
return $q(function (resolve, reject) {
idb.webkitGetDatabaseNames().onsuccess =
function (sender, args) {
if (sender.target.result
&& sender.target.result.length > 0) {
var db = sender.target.result[0];
console.log("deleting " + db);
var request = idb.deleteDatabase(db);
request.onsuccess = function () {
console.log('database ' + db + ' deleted.');
resolve();
};
request.onerror = function () {
reject();
};
} else {
console.log("Nothing to delete");
resolve();
};
};
});
}
function open(dbName) {
return $q(function (resolve, reject) {
var request = idb.open(dbName, svc.dbVersion);
request.onupgradeneeded = function (e) {
var db = e.target.result;
console.log("creating new " + db.name);
e.target.transaction.onerror = function (e) {
console.log(e);
};
db.createObjectStore("table1", {
keyPath: "id"
});
db.createObjectStore("table2", {
keyPath: "id"
});
db.createObjectStore("table3", {
keyPath: "id"
});
};
request.onsuccess = function (e) {
console.log('database ' + dbName + ' open.');
svc.db = e.target.result;
resolve();
};
request.onerror = function () {
reject();
};
});
}
}]);
angular.module('IDBTest')
.factory('$exceptionHandler', ['$log', function ($log) {
return function (exception, cause) {
throw exception;
};
}]);
angular.module('IDBTest')
.run(['initSvc', function (initSvc) {
initSvc.deleteDB()
.then(initSvc.open('testDatabase'))
.then(function () {
console.log(initSvc.db.name + ' initialized');
});
}]);
This fiddle shows my expectation that
Any databases created are deleted.
then
A database is open triggering an onupgradeneeded
then
The database is referenced
Unfortunately the then statments seem to get called BEFORE the promises are resolved in the onsuccess methods of the IDB calls.
To recreate, run the jsfiddle with the console open. May have to run it a couple times to get the exception, but it fails most times, because the last then clause is called before the onsuccess on the database open is called.
Any ideas?
I believe the issue is with the Promise Chain. The documentation from Angular is a bit confusing, but it seems as though if the return value of the callback method is a promise, it will resolve with a value; not a promise. Thus, breaking the chain.
From Angular Promise 'Then' Method Documentation:
then(successCallback, errorCallback, notifyCallback) – regardless of
when the promise was or will be resolved or rejected, then calls one
of the success or error callbacks asynchronously as soon as the result
is available. The callbacks are called with a single argument: the
result or rejection reason. Additionally, the notify callback may be
called zero or more times to provide a progress indication, before the
promise is resolved or rejected.
This method returns a new promise which is resolved or rejected via
the return value of the successCallback, errorCallback (unless that
value is a promise, in which case it is resolved with the value which
is resolved in that promise using promise chaining). It also notifies
via the return value of the notifyCallback method. The promise cannot
be resolved or rejected from the notifyCallback method.
I'm able to get it to initialize with this:
angular.module('IDBTest')
.run(['initSvc', function (initSvc) {
initSvc.deleteDB()
.then(function() {
return initSvc.open('testDatabase');
})
.then(function () {
console.log(initSvc.db.name + ' initialized');
});
}]);
I'm using the async.js library to achieve an stream of asynchronous requests.
The code below works fine.
async.each(
//--- Collection of models to save ---//
collSaveData,
function(model, callback){
model.save([], {success: function(){
callback();
}});
},
function(err){
console.log('finished');
});
How can I return a promise?
I mean, something in a fashion like this:
var promise = async.each(
//--- Collection of models to save ---//
collSaveData,
function(model, callback){
model.save([], {success: function(){
callback();
}});
},
function(err){
console.log('finished');
});
You probably don't need async.js to issue you calls and synchronize them. Combine the objects returned by Model.save with $.when to produce a general promise :
var promises = _.invoke(collSaveData, 'save');
var promise = $.when.apply(null, promises);
promise.then(function() {
console.log('all done');
});
And a Fiddle http://jsfiddle.net/nikoshr/Z3Ezw/
You can customize how you handle the responses from each save, for example:
var promises = _.map(collSaveData, function(m) {
return m.save().then(function(response) {
console.log('saved', m);
});
});
The key is to return a promise for each model. http://jsfiddle.net/nikoshr/Z3Ezw/2/
Not actually used async before but looking through the docs it makes no reference to returning a promise so you would have to wrap the asyn.each with a function that did return a promise and then the success/error callback could then just resolve or reject that promise
here is a quick example that should work
//wrap the async each and return a promise from this call
var promiseAsync = function(openfiles, saveFile) {
var defer = $.Deferred();
async.each(
openfiles, saveFile, function(err) {
if (err) {
defer.reject(err);
} else {
defer.resolve();
}
});
return defer.promise();
}
//now it can be used like a normal promise
var promise = promiseAsync(collSaveData, function(model, callback) {
model.save([], {
success: function() {
callback();
}
});
});
$.when(promise).done(function(){
//whatever
}).fail(function(err){
//error
});
make promiseAsync available throughout your app (attach it to Backbone somewhere, underscore mixin,helper/utility module??) and then you can always use it.
Update: fiddle based on #nikoshr's fiddle (for setting up the page) (going to start using fiddles over code pen now like the fact you can have a console in the browser) http://jsfiddle.net/leighking2/7xf7v/