I'm building a library which uses asynchronous resources. I want the library to be loadable with AMD/require.
I've seen this discussion from 2011 about promises support, but it went nowhere.
I've seen plugins like async and require-promise - but I don't know how I feel about a library installing plugins.
I'm interested to know if anything has changed in terms of loading resources and/or dependent scripts. If promises are not supported, is there another way to signal requirejs that a script is ready in an asynchronous way?
I dived into the source of require.js, and I didn't find a way to make an asynchronous define. Plugins have the capability, but simple defines do not. I think the reason behind this is that async circular dependencies are a bit tricky.
However it is not impossible to write a mini async require-like library:
(function(context) {
const modules = new Map();
function require(name) {
return Promise.resolve(null).then(function() {
const module = modules.get(name);
if (!module) throw new Error("Undefined module");
if (module.promise) {
return module.exports;
} else {
return module.promise = Promise.all(
module.deps.map(internalRequire)
).then(function(args) {
return module.factory.apply(context, args);
}).then(function(defExport) {
if (defExport !== undefined) {
module.exports.default = defExport;
}
return module.exports;
});
}
function internalRequire(name) {
return (name === 'exports') ? module.exports : require(name);
}
});
}
function define(name, deps, factory) {
if (modules.has(name)) throw new Error("Redefined module");
modules.set(name, {
name: name,
deps: deps,
factory: factory,
exports: {},
promise: null
});
}
context.require = require;
context.define = define;
})(self);
Usage:
define('A', ['B'], function(dep) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve("A(" + dep.default + ")");
}, 100);
});
});
define('B', ['C'], function(dep) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve("B(" + dep.default + ")");
}, 100);
});
});
define('C', ['A'], function(dep) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve("C(" + dep.default + ")");
}, 100);
});
});
require("A").then(function(s) {
console.log(s);
});
Related
I am using Ember even though I am making changes but when I run the ./watch its not producing the JavaScript file with new code - its annoying - I am making the manual change but that's not correct solution right? I made the change to the file even by opening in folder and manually updated the code on ember file - still its writing the old code in app.js file.
What could be the reason? I am using Web Pack to run ./watch and generate the app.js file.
Here is my Ember js code:
export default () => {
IMS.registerController("case.details.assignedinspector.edit", {
caseDetails: Ember.inject.controller('caseDetails'),
caseId: Ember.computed.alias('caseDetails.model.imscase.id'),
clearForm: function () {
$('.modal').modal('hide');
},
actions: {
close: function (id) {
$('.modal').modal('hide');
this.transitionToRoute('case.details');
},
async save() {
var scope = this;
//validating form
if ($("#edit-assignedinspector-form").validate()) {
var data = {
AssignedToInvestigators: Ember.get(scope, 'model.imscase.assignedToInvestigators'), //AssignedToInspectorId: $("#assignedInspectorSelect").chosen().val(),
CaseId: this.get('caseId')
};
try {
let response = await this.api('Case/UpdateAssignedInvestigators').post(data);
$('.modal').modal('hide');
toastr.success("Assigned Inspector Edit Saved.");
scope.transitionToRoute("case.details");
scope.get('caseDetails').refreshData();
scope.clearForm();
} catch (ex) {
toastr.error("Error saving Assigned Inspector.");
}
}
else {
toastr.warning("Please fix the form", "Validation failed");
}
},
didInsert: function () {
$('#edit-assignedinspector-modal').modal();
}
}
});
}
Here is how its generating the old code in app.js file:
save: function () {
var _save = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee() {
var scope, data, response;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
scope = this; //validating form
if (!$("#edit-assignedinspector-form").validate()) {
_context.next = 19;
break;
}
data = {
AssignedToInspectorId: $("#assignedInspectorSelect").chosen().val(),
CaseId: this.get('caseId')
};
_context.prev = 3;
_context.next = 6;
return this.api('Case/UpdateAssignedInspector').post(data);
case 6:
response = _context.sent;
$('.modal').modal('hide');
toastr.success("Assigned Inspector Edit Saved.");
scope.transitionToRoute("case.details");
scope.get('caseDetails').refreshData();
scope.clearForm();
_context.next = 17;
break;
case 14:
_context.prev = 14;
_context.t0 = _context["catch"](3);
toastr.error("Error saving Assigned Inspector.");
case 17:
_context.next = 20;
break;
case 19:
toastr.warning("Please fix the form", "Validation failed");
case 20:
case "end":
return _context.stop();
}
}
}, _callee, this, [[3, 14]]);
}));
function save() {
return _save.apply(this, arguments);
}
return save;
}()
It is supposed to call Case/UpdateAssignedInvestigators instead its still calling the Case/UpdateAssignedInspector, which is incorrect.
I dynamically create collections with this method:
createPlist: function(jid) {
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Plist[jid].insert({
...,
...,
public:true,
uid:this.userId
});
}
Then I am wanting to publish these selectively, and I am attempting to do it via a method:
getPlist: function(jid,pid) {
// var future = new Future();
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Meteor.publish(pid, function() {
console.log(Plist[jid].find({}));
// future["return"](Plist[jid].find({}));
return Plist[jid].find();
});
// return future.wait();
},
This returns 'undefined' to my Template helper, and returns nothing (i.e. waits forever) using Future.
Any user can log in and create a Plist collection, which can be either public or not. A user can also subscribe to any collection where public is true. The variable jid is passed to the method 'getPlist' from the template. It is stored in the user's Session.
Thanks! I hope I have explained it well enough!
And of course the template:
Template.plist.helpers({
getPlist: function() {
Pl = []
jid = Session.get('jid');
//alert(jid);
pid = "pl_"+jid;
// console.log(pid);
Meteor.call('getPlist', jid, pid, function(err,res) {
console.log(res); //returns undefined
try {
Pl[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log(e);
}
Meteor.subscribe(pid);
// return Pl[jid].find({}).fetch();
});
}
I've been testing on http calls with meteor, I used nitrous (because I had no access to my dev laptop during the weekend) and it worked fine.
But when I tried to run from my local pc it returns:
Exception in delivering result of invoking 'getMatch': TypeError:
Cannot read property 'duration' of undefined.
Any ideas of what could be the cause?
Method definition:
Dota = {};
Dota.getMatch = function() {
if (!Meteor.settings.steamToken)
throw new Meteor.Error(500, 'Enter a valid Steam Token in Meteor.settings');
var matchResponse = Meteor.http.get(
"https://api.steampowered.com/IDOTA2Match_570/GetMatchDetails/V001/?",
{
params:{
"match_id": "1305454585",
"key": Meteor.settings.steamToken
}
}
);
if (matchResponse.statusCode === 200) {
return matchResponse.data.result
}
else {
throw new Meteor.Error(500, "getMatch failed with error: "+matchResponse.statusCode);
}
}
Meteor.methods({
'getMatch': function(){
return Dota.getMatch();
}
})
Calling the method:
Meteor.call('getMatch', function(error, result){
var duration = numeral(result.duration).format('00:00:00');
Session.set('duration', duration);
var winner = Meteor.myFunctions.getWinner(result.radiant_win);
Session.set('winner', winner);
});
Template.layout.helpers({
winner: function () {
return Session.get('winner');
},
duration: function () {
return Session.get('duration');
}
});
Found a solution, I changed the location of
Meteor.methods({
'getMatch': function(){
return Dota.getMatch();
}
})
to server/server.js (I had it in packages/dota/dota.js) and now it works! Thanks #user3374348 for helping!
I am using keystone#0.2.32. I would like to change the post category to a tree structure. The below code is running well except when I create a category, it goes into a deadlock:
var keystone = require('keystone'),
Types = keystone.Field.Types;
/**
* PostCategory Model
* ==================
*/
var PostCategory = new keystone.List('PostCategory', {
autokey: { from: 'name', path: 'key', unique: true }
});
PostCategory.add({
name: { type: String, required: true },
parent: { type: Types.Relationship, ref: 'PostCategory' },
parentTree: { type: Types.Relationship, ref: 'PostCategory', many: true }
});
PostCategory.relationship({ ref: 'Post', path: 'categories' });
PostCategory.scanTree = function(item, obj, done) {
if(item.parent){
PostCategory.model.find().where('_id', item.parent).exec(function(err, cats) {
if(cats.length){
obj.parentTree.push(cats[0]);
PostCategory.scanTree(cats[0], obj, done);
}
});
}else{
done();
}
}
PostCategory.schema.pre('save', true, function (next, done) { //Parallel middleware, waiting done to be call
if (this.isModified('parent')) {
this.parentTree = [];
if(this.parent != null){
this.parentTree.push(this.parent);
PostCategory.scanTree(this, this, done);
}else
process.nextTick(done);
}else
process.nextTick(done); //here is deadlock.
next();
});
PostCategory.defaultColumns = 'name, parentTree';
PostCategory.register();
Thanks so much.
As I explained on the issue you logged on Keystone here: https://github.com/keystonejs/keystone/issues/759
This appears to be a reproducible bug in mongoose that prevents middleware from resolving when:
Parallel middleware runs that executes a query, followed by
Serial middleware runs that executes a query
Changing Keystone's autokey middleware to run in parallel mode may cause bugs in other use cases, so cannot be done. The answer is to implement your parentTree middleware in serial mode instead of parallel mode.
Also, some other things I noticed:
There is a bug in your middleware, where the first parent is added to the array twice.
The scanTree method would be better implemented as a method on the schama
You can use the findById method for a simpler parent query
The schema method looks like this:
PostCategory.schema.methods.addParents = function(target, done) {
if (this.parent) {
PostCategory.model.findById(this.parent, function(err, parent) {
if (parent) {
target.parentTree.push(parent.id);
parent.addParents(target, done);
}
});
} else {
done();
}
}
And the fixed middleware looks like this:
PostCategory.schema.pre('save', function(done) {
if (this.isModified('parent')) {
this.parentTree = [];
if (this.parent != null) {
PostCategory.scanTree(this, this, done);
} else {
process.nextTick(done);
}
} else {
process.nextTick(done);
}
});
I think it's a bug of keystone.js. I have changed schemaPlugins.js 104 line
from
this.schema.pre('save', function(next) {
to
this.schema.pre('save', true, function(next, done) {
and change from line 124 to the following,
// if has a value and is unmodified or fixed, don't update it
if ((!modified || autokey.fixed) && this.get(autokey.path)) {
process.nextTick(done);
return next();
}
var newKey = utils.slug(values.join(' ')) || this.id;
if (autokey.unique) {
r = getUniqueKey(this, newKey, done);
next();
return r;
} else {
this.set(autokey.path, newKey);
process.nextTick(done);
return next();
}
It works.
In previous versions of angularFire, it was possible to secure selected routes by using "authRequired" and "pathTo" with Angular's $routeProvider. These no longer appear to work with AngularFire 0.6.0. What is the equivalent parameter/technique in Angular 0.6.0?
Routing was moved out of angularFire for the same reasons it was moved out of the core of Angular--to be less opinionated in how routing is conducted and which lib you should use.
You can still include routing by grabbing the module from angularFire-seed, which is plug-and-play ready.
The steps are:
add ngRoute and routeSecurity to your app dependencies
declare the loginRedirectPath constant
add authRequired where appropriate
Example:
// add routeSecurity to your dependency libs
angular.module('myApp', [..., 'ngRoute', 'firebase', 'routeSecurity']);
// declare the loginRedirectPath variable
angular.module('myApp').constant('loginRedirectPath', '/login')
// put authRequired in your routes
$routeProvider.when('/account', {
authRequired: true, // must authenticate before viewing this page
templateUrl: 'partials/account.html',
controller: 'AccountCtrl'
});
// live long and prosper
Here's a hard copy of the module as of 0.6.0 for compliance with SO policy; refer directly to the source for a current version:
(function(angular) {
angular.module('routeSecurity', [])
.run(['$injector', '$location', '$rootScope', 'loginRedirectPath', function($injector, $location, $rootScope, loginRedirectPath) {
if( $injector.has('$route') ) {
new RouteSecurityManager($location, $rootScope, $injector.get('$route'), loginRedirectPath);
}
}]);
function RouteSecurityManager($location, $rootScope, $route, path) {
this._route = $route;
this._location = $location;
this._rootScope = $rootScope;
this._loginPath = path;
this._redirectTo = null;
this._authenticated = !!($rootScope.auth && $rootScope.auth.user);
this._init();
}
RouteSecurityManager.prototype = {
_init: function() {
var self = this;
this._checkCurrent();
// Set up a handler for all future route changes, so we can check
// if authentication is required.
self._rootScope.$on("$routeChangeStart", function(e, next) {
self._authRequiredRedirect(next, self._loginPath);
});
self._rootScope.$on('$firebaseSimpleLogin:login', angular.bind(this, this._login));
self._rootScope.$on('$firebaseSimpleLogin:logout', angular.bind(this, this._logout));
self._rootScope.$on('$firebaseSimpleLogin:error', angular.bind(this, this._error));
},
_checkCurrent: function() {
// Check if the current page requires authentication.
if (this._route.current) {
this._authRequiredRedirect(this._route.current, this._loginPath);
}
},
_login: function() {
this._authenticated = true;
if( this._redirectTo ) {
this._redirect(this._redirectTo);
this._redirectTo = null;
}
else if( this._location.path() === this._loginPath ) {
this._location.replace();
this._location.path('/');
}
},
_logout: function() {
this._authenticated = false;
this._checkCurrent();
},
_error: function() {
if( !this._rootScope.auth || !this._rootScope.auth.user ) {
this._authenticated = false;
}
this._checkCurrent();
},
_redirect: function(path) {
this._location.replace();
this._location.path(path);
},
// A function to check whether the current path requires authentication,
// and if so, whether a redirect to a login page is needed.
_authRequiredRedirect: function(route, path) {
if (route.authRequired && !this._authenticated){
if (route.pathTo === undefined) {
this._redirectTo = this._location.path();
} else {
this._redirectTo = route.pathTo === path ? "/" : route.pathTo;
}
this._redirect(path);
}
else if( this._authenticated && this._location.path() === this._loginPath ) {
this._redirect('/');
}
}
};
})(angular);