Detecting unexpected properties - javascript

Often I use objects to pass around a set of options.
In 99.9% of these cases I expect these objects to only contain a subset of the property I use. If an unexpected property is present, it means almost always that there's a typo or a logical error.
Is there a simple way to make sure that the option objects don't contain unexpected properties? I would only really need this during testing and debugging, so it doesn't have to be efficient.
Example:
function log( text, opts={} ) {
const {times=1, silent=false, logger=console} = opts
if (silent) return
for (let i=0; i<times; i++) {
logger.log( text )
}
}
// all good here
log( "Hello World!", {times:1} )
// "slient" is not a valid option. It's a typo.
// I want this call to throw an exception.
log( "Bye World!", {times:2, slient:true} )
I know that I can implement it through a function that receives the names of the expected properties:
function testOpts( opts={}, optNames=[] ) { /* ... */ }
testOpts( opts, ["times", "silent", "logger"] )
But maintaining this is boring and error prone: every time I change an option I need to update those parameters. I tried it and too often I forget to update it, so my function keeps accepting parameters that I removed, and this is something I want to avoid.
Is there a better solution that lets me write the property names only once?

If you only want to write them once, the only way to do that is in the log function where you are using them. There is indeed a way to do that: use rest syntax in the destructuring, and test that there are no other properties than the expected (destructured) ones:
function log( text, opts={} ) {
const {times=1, silent=false, logger=console, ...rest} = opts
// ^^^^^^^
assertEmpty(rest);
if (silent) return
for (let i=0; i<times; i++) {
logger.log( text )
}
}
function assertEmpty(val) {
const keys = Object.keys(val);
if (keys.length) {
throw new RangeError(`Unexpected properties ${keys.join(', ')} in object`);
}
}
Other than that, use a static analysis tool such as TypeScript or Flow, they will easily catch these mistakes.

Related

Pass an object that has an optional property to a function that guarantee it will not be undefined but editor/compiler still think it can be undefined

function f1(
toolbox: {
tool1?: Tool1,
tool2?: Tool2,
}
) {
if (!toolbox.tool1) {
toolbox.tool1 = fetchTool1();
}
if (!toolbox.tool2) {
toolbox.tool2 = fetchTool2();
}
// Do something else
}
function f2(
toolbox: {
tool1?: Tool1,
tool2?: Tool2,
}
) {
f1(toolbox);
// tool1 and tool2 are no longer undefined.
const x = toolbox.tool1.value // Editor shows error, tool1 may be undefined.
}
The design above passes a toolbox object to different functions, so the program doesn't need to fetch the tools that are already fetched by other functions. The problem is, even if I know for sure toolbox.tool1 and toolbox.tool2 will not be undefined after calling f1, the editor still shows error.
I know I can use exclamation mark like const x = toolbox.tool1!.value, or maybe just turn off the warning. But I'm looking for a way that is more friendly to the default type checking.
I have tried let f1 return the toolbox that has no question mark in it, and call toolbox = f1(toolbox). It doesn't work, the line const x = toolbox.tool1.value still shows error.
You're looking for asserts.
f1 now looks like this:
function f1(
toolbox: {
tool1?: Tool1,
tool2?: Tool2,
}
): asserts toolbox is { tool1: Tool1; tool2: Tool2 } {
You can think of this as "changing" the type in the scope it was called:
f1(toolbox);
toolbox // now { tool1: Tool1; tool2: Tool2 }
const x = toolbox.tool1.value; // fine
Playground

transpiler battle: breaking out of nested function, with vs without throw

I have just finished writing "version 0" of my first (toy) transpiler. It works. It turns a string of "pseudo JavaScript" (JavaScript with an additional feature) into a string of runnable JavaScript. Now, I want to improve it.
The work area possibly most interesting for other SO users is this: The compiled code (i.e., output of my transpiler) does not heed a coding style recommendation as given in an accepted answer to some earlier SO question. If I would have at my hands a second transpiler where that coding style recommendation is heeded, I could make an informed decision regarding which branch is more promising to continue to develop on - I'd like to compare the 2 braches regarding performance, development time needed, amount of bugs, and so on, and decide based on that.
Let me tell you about the "additional JS feature" my transpiler deals with: "nested return". Consider closures / nested functions like so
function myOuterFunc(){
... code ...
function innerFunc(){
... code ...
}
... code ...
}
(note that above '...code...' is supposed to include every possible JS code including more nested function declarations, so myOuterFunc is not necessarily the direct parent of innerFunc)
In above situation, suppose you desire to return a result from myOuterFunc from somewhere inside - not necessarily directly inside - innerFunc
With "nested return" implemented, you could then write simply
return.myOuterFunc result
Here is an exmalpe of a (not-runnable) function using this feature and doing something meaningful
function multiDimensionalFind(isNeedle, haystack) {
// haystack is an array of arrays
// loop (recursively) through all ways of picking one element from each array in haystack
// feed the picked elements as array to isNeedle and return immediately when isNeedle gives true
// with those picked elements being the result, i.e. the 'found needle'
var LEVEL = haystack.length;
function inner(stack) {
var level = stack.length;
if (level >= LEVEL) {
if (isNeedle(stack)) return.multiDimensionalFind stack;
} else {
var arr = haystack[level];
for (var i = 0; i < arr.length; i++) {
inner(stack.concat([arr[i]]));
}
}
}
inner([]);
return 'not found'
}
And here is the (runnable) code my transpiler automatically produces from that (comments were added/remove later, obviously), followed by some code testing if that function does what it claims to do (and it does, as you can convince yourself.)
///////////// the function /////////////////
function multiDimensionalFind(isNeedle, haystack) {
try {
var LEVEL = haystack.length;
function inner(stack) {
var level = stack.length;
if (level >= LEVEL) {
if (isNeedle(stack)) throw stack;
} else {
var arr = haystack[level];
for (var i = 0; i < arr.length; i++) {
inner(stack.concat([arr[i]]));
}
}
}
inner([]);
return 'not found'
} catch(e){
// make sure "genuine" errors don't get destroyed or mishandled
if (e instanceof Error) throw e; else return e;
}
}
////////////////// test it //////////////////
content = document.getElementById('content');
function log2console(){
var digits = [0,1];
var haystack = [digits,digits,digits,digits,digits];
var str = '';
function isNeedle(stack){
str = str + ', ' + stack.join('')
return false;
}
multiDimensionalFind(isNeedle, haystack);
content.textContent = str;
}
function find71529(){ // second button
var digits = [0,1,2,3,4,5,6,7,8,9]
var haystack = [digits,digits,digits,digits,digits]
function isNeedle(stack){
return stack.reduce(function(b,i){ return 10*b+i; }, 0) === 71529
// returns true iff the stack contains [7,1,5,2,9]
}
content.textContent = multiDimensionalFind(
isNeedle, haystack
).join('_')
}
<button onclick='log2console()'>print binary numbers with 5 digits</button>
<br>
<button onclick='find71529()'>find something is 5d space</button>
<div id='content'></div>
You can play around with my transpiler in this fiddle here. I'm using the esprima library, the escodegen.js library on top of esprima, a teeny tiny work in progress abstract syntax tree generation library of my own (see the script tags in the fiddle). The code which is not library, and not UI code, i.e. the "real meat" of the transpiler has less than 100 lines (see function transpile). So this might be a lot less complicated than you thought.
I can't remember where I had seen the style recommendation, but I am certain that it was actually in multiple places. If you know or come across one such question, I invite you to be so kind to put the link into a comment under the question, I will mark useful. So far, there is one link, thank you Barmar.
You might ask why I even bothered to write a "non-compliant" transpiler first, and did not go for the "compliant" version straight away. That has to do with the estimated amount of work. I estimate the amount of work to be much larger for the "compliant" version. So much so, that it didn't really seem worthwhile to embark on such an endeavor. I would very much like to know whether this assessment of the amount of work is correct or incorrect. Thus the question. Please do not insinuate a rhetorical, or even a dishonest motive for the question; however weird it might sound to some, it really is the case that I'd like to be proven wrong, so please be so kind not to assume I'm "just saying that" for whatever reason, you'd be doing me an injustice. This is, of all the questions I asked on SO so far, by far the one I've put the most work into. And, if you ask me, it is by far the best question I have ever asked here.
Apart from someone assisting me in writing the "compliant" version of the transpiler, I'm also interested (albeit to a lesser degree) in anything objectively demonstrable which stands a chance to convince me that the "non-compliant" way is the wrong way. Speed tests (with links to jsperf), reproducible bug reports, things of that sort.
I should mention the speed tests I have done myself so far:
first test, second test
loosely related question
The better way really is to use return (if you can't completely refactor the tower). It makes it clear what you're doing and when you're returning a value from the intermediate functions; you can tell by looking at those functions where the result may be provided. In contrast, using throw is invisible in those intermediate layers; it magically bypassing them in a way designed for error conditions.
If you don't want to do that, I don't think you have a reasonable alternative other than throw. I wondered about going down the route of generator functions or similar, but I think that would just complicate things more, not less.
Using return doesn't markedly complicate the example code, and does (to my eye) make it clearer what's going on and when results are potentially expected.
function wrapper(){
function first(){
function second(){
function third(){
doStuff4();
some loop {
var result = ...
if (something) return result;
}
}
doStuff2();
let result = third();
if (result) {
return result;
}
doStuff3();
return third();
}
doStuff1();
return second();
}
return first() || "not found";
}
(In the above, result is tested for truthiness; substitute something else if appropriate.)
Ok, here is another approach with use of all async power of JavaScript... So basically I've recreated your nested functions but with use of Promise/await technique. You will get result only once, the first time you'll resolve it from any level of nested functions. Everything else will be GC. Check it out:
// Create async function
(async () => {
const fnStack = (val) => {
return new Promise((resolve, reject) => {
// Worker functions.
// Could be async!
const doStuff1 = (val) => val + 1;
const doStuff2 = (val) => val * 2;
const doStuff3 = (val) => val * -1; // This will not affect result
const doStuff4 = (val) => val + 1000;
// Nested hell
function first() {
function second() {
function third() {
val = doStuff4(val);
// Some loop
for(let i = 0; i < 1000; i++) {
if(i === 500) {
// Here we got our result
// Resolve it!
resolve(val);
}
}
}
val = doStuff2(val);
third();
// Below code will not affect
// resolved result in third() above
val = doStuff3(val);
third();
}
val = doStuff1(val);
second();
}
//
first();
});
}
// Run and get value
const val = await fnStack(5);
// We get our result ones
console.log(val);
})();
I think you should use arrays;
const funcs = [first, second, third];
for(let i = 0; i < funcs.length; ++i){
const result = funcs[i]();
if (result) break;
}
You should return only from function that has the result.
Sometime later, when I get around to it, I'll add instructions here on how to use my abstract syntax tree generation library which I used for my transpiler, maybe even start a little towards the other version, maybe explaining in more detail what the things are which make me think that it is more work.
old version follows (will be deleted soon)
If the feature is used multiple times we'll need something like this:
function NestedThrowee(funcName, value){
this.funcName = funcName;
this.value = value;
}
return.someFunctionName someReturnValue (before compilation) will give (after compliation) something like
var toBeThrown = new NestedThrowee("someFunctionName", someReturnValue);
And inside the generated catch block
if (e instanceof Error){
throw e; // re-throw "genuine" Error
} else {
if (e instance of NestedThrowee){
if (e.funcName === ... the name of the function to which
this catch block here belongs ...) return e.value;
throw new Error('something happened which mathheadinclouds deemed impossible');
}
In the general case, it is necessary to wrap the result (or 'throwee') like shown above, because there could be multiple, possibly nested "nested returns", and we have to take care that the catch phrase and caught object of type NestedThrowee match (by function name).

Get the name of a factory function when passed with parameters (weird)

Okay, so I have a problem.
SHORT VERSION:
I want to do this:
const createThing = function( behaviours(intensities) ){
return {
behaviour1: behaviour1(intensity1),
behaviour2: behaviour2(intensity2)
and so on for each behaviour(intensity) passed as an argument
}
}
//so when doing:
const wtf = createThing( cat(loud), dog(whereistheball), bird(dee) );
// wtf should be:
{
cat: cat(loud),
dog: dog(whereistheball),
bird: bird(dee)
}
I've tryied among other things something like this:
const createThing = function( behaviours(intensities) ){
return {
for(a in arguments){
[a.name]: a;
}
}
}
I've been trying a lot of different ways to do this on the past week with no success. Can anyone help me?
LONG VERSION:
Okay so I have a magnet behaviour factory function and a particle factory function, it looks like this
const magnet = funtion(intensity) {
// returns a magnetic object with that intensity
}
const createParticle = function( location, static or dynamic, behaviours ){
// returns a particle object with the properties and behaviours listed above
}
The problem is I can't get the behaviours part to work. By now I have a magnetic behaviour factory, but I also want to have an eletrical, gravitacional, random etc. I want the particle object to get the behavior name as a new property key and the object it creates as this property value when this behaviour is passed as an argument to the particle factory function, something like this:
const particle1 = createParticle ( location, dynamic, magnet(intensity) )
//should return
{
location,
dynamic,
magnet: magnet(intensity)
}
or even
const particle2 = createParticle ( location, dynamic, magnet(intensity), eletric(intensity) )
//should return
{
location,
dynamic,
magnet: magnet(intensity),
eletric: eletric(intensity)
}
and so on.
I tried using the method function.name, but it is not possible since when I pass the behaviour function as a parameter to the particle, it evaluates into an object. I tried using a callback function and then using function.name but it doesn't do anything since I still need to pass the behavior function along with its parameters to the particle factory.
Is it even possible??? How???
No, this is not possible. Not unless cat/dog/bird/magnet/eletric all return an object that contains the name of the respective factory.
In particular:
function createParticle(...args) {
const particle = { kind: 'particle' };
for (const element of args)
particle[element.kind] = element;
return particle;
}
If you are using classes/constructors+prototypes, you can of course use the implicit .constructor.name instead of the .kind property that I chose in the example above.

Custom console log function, a console.log wrapper

function log( msgOrObj ){
if(dev_mode){
console.log({
'message': msgOrObj,
'caller': arguments.callee.caller.toString()
});
}
}
So, I have attempted to write a simple custom console log function (as above). However I am struggling to find which file and line the caller came from. The most I can see is the function that called it.
Has anyone done anything similar? Or is this even possible?
example used in somescript.js from line 70:
log('some very important message!')
Yes but it's very hacky and not cross browser-safe. You can use this as a starting point. It borrows from this answer.
window.trace = function stackTrace() {
var err = new Error();
return err.stack;
}
window.my_log = function (x) {
var line = trace();
var lines = line.split("\n");
console.log(x + " " + lines[2].substring(lines[2].indexOf("("), lines[2].lastIndexOf(")") + 1))
}
window.my_log("What light through yonder window breaks?")
Produces:
What light through yonder window breaks? (<anonymous>:2:42)
The only way I've seen to reliably extract this kind of info is to throw an error and then extract the caller info from the stack trace, something along the lines of:
function log( msgOrObj ){
if(dev_mode){
try {
in_val_id(); // force an error by calling an non-existent method
catch(err) {
// some regex/string manipulation here to extract function name
// line num, etc. from err.stack
var caller = ...
var lineNo = ...
}
console.log({
'message': msgOrObj,
'caller': caller,
'lineNo': lineNo
});
}
}
The stack in Chrome is in this form:
ReferenceError: in_val_id is not defined
at log (<anonymous>:4:13)
at <anonymous>:2:14
at <anonymous>:2:28
at Object.InjectedScript._evaluateOn (<anonymous>:581:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:540:52)
at Object.InjectedScript.evaluate (<anonymous>:459:21)
you can extract the function name with:
caller = err.stack.split('\n')[3].split('at ')[1].split(' (')[0];
using a regex here might be more performant. You'll probably need different approaches to extract this info with different browsers.
A word of warning though; throwing and handling errors is expensive so outputting a lot of log messages in this way is likely to impact on general performance, though this may be acceptable if it is specifically for a debug mode
So, this is what I went for in the end (where shout is a bespoke function only running in dev mode):
function log( msgOrObj ){
if(dev_mode){
if( typeof(window.console) != 'undefined' ){
try { invalidfunctionthrowanerrorplease(); }
catch(err) { var logStack = err.stack; }
var fullTrace = logStack.split('\n');
for( var i = 0 ; i < fullTrace.length ; ++i ){
fullTrace[i] = fullTrace[i].replace(/\s+/g, ' ');
}
var caller = fullTrace[1],
callerParts = caller.split('#'),
line = '';
//CHROME & SAFARI
if( callerParts.length == 1 ){
callerParts = fullTrace[2].split('('), caller = false;
//we have an object caller
if( callerParts.length > 1 ){
caller = callerParts[0].replace('at Object.','');
line = callerParts[1].split(':');
line = line[2];
}
//called from outside of an object
else {
callerParts[0] = callerParts[0].replace('at ','');
callerParts = callerParts[0].split(':');
caller = callerParts[0]+callerParts[1];
line = callerParts[2];
}
}
//FIREFOX
else {
var callerParts2 = callerParts[1].split(':');
line = callerParts2.pop();
callerParts[1] = callerParts2.join(':');
caller = (callerParts[0] == '') ? callerParts[1] : callerParts[0];
}
console.log( ' ' );
console.warn( 'Console log: '+ caller + ' ( line '+ line +' )' );
console.log( msgOrObj );
console.log({'Full trace:': fullTrace });
console.log( ' ' );
} else {
shout('This browser does not support console.log!')
}
}
}
log() when declared before the rest of the application can be called anywhere from within the app and give the developer all the information required plus will not run out of dev mode.
(http://webconfiguration.blogspot.co.uk/2013/12/javascript-console-log-wrapper-with.html)
Instead of using arguments you can do
function log( msg ) {
if (dev_mode) {
var e = new Error(msg);
console.log(e.stack);
}
}
This will show you the order in which all the functions were called (including line numbers and files). You can just ignore the first 2 lines of the stack (one will contain the error message and one will contain the log function since you are creating the error object within the function).
If you want a more robust logging - use A proper wrapper for console.log with correct line number? as #DoXicK suggested
I use this in Node and its particularly effective. Console.log is just a function it can be reassigned as well as stored for safe keeping and returned back after we are done. I've no reason to believe this would not work in a browser too.
//Store console.log function in an object so
//we can still use it.
theConsole = {};
theConsole.log = console.log;
//This function is called when console.log occurs
//arguments[0] is what would normally be printed.
console.log = function(){
theConsole.log(">" + arguments[0]);
}
//Call our console.log wrapper
console.log("Testing testing 123");
console.log("Check one two");
//Put back the way it was
console.log = theConsole.log;
console.log("Now normal");
There are a couple options to quickly go about this.
1 - Use console.error
Not very convenient, actual errors will go unnoticed and seeing a lot of red in your console output may have a negative impact on your morale. In short - don't use, unless it's for a very small script or some test
2 - Add your log method to the prototype of Object
to get the current scope/ module name/ etc. Much more flexible and elegant.
Object.prototype.log = function(message){
console.log({
'message': message,
'caller': this,
'stack':arguments.callee.caller.toString()
});
};
Use (anywhere) as:
this.log("foo");
You could add the techniques from this thread to get exact function name inside your object, as so:
var callerFunc = arguments.callee.caller.toString();
callerFuncName = (callerFunc.substring(callerFunc.indexOf("function") + 9, callerFunc.indexOf("(")) || "anoynmous");
Yet make sure your scope is named... forcing you to go from this:
Module.method = function(){}
To this:
Module.method = function method(){}
As for line numbers, calling (new Error()) will give you access to the line number where it was called - and not even on all browsers.
Creating an elegant debugging function is a piece of work
As much as I hate to admit it, the other answer implying reg-exps over a try result seems to be the faster cure for your problem.
It seems you all are struggling too much. I have a simple one-line solution:-
//Just do this, that I have done below: HAVE FUN
var log=console.log;
log(`So this way is known as Aniket's way.`);
log(`Don't be too serious this is just the fun way of doing same thing`);
log(`Thank You`)
Try this
window.log = (() => {
if (dev_mode) {
return console.log;
} else return () => {};
})();

can I emulate a C-like array of pointers in javascript?

I'd like to be able to store the addresses of a bunch of different variables in an array. This allows me to access the variables by name or iterate through them if I need to. Is this possible in JS?
(function(ns){
ns.obj = new function(){
var foo = "foo";
var bar = "bar";
//i really want this:
//var ary = [&foo, &bar];
var ary = [foo, bar];
this.print = function() {
console.log( foo );
console.log( bar );
}
this.setFoo = function( newFoo ) {
//i really want this:
//*(ary[0]) = newFoo;
ary[0] = newFoo;
}
this.printAry = function() {
for( var i=0; i < ary.length; ++i ) {
console.log( ary[i] );
}
}
};
}(window.ns = window.ns || {}) );
ns.obj.print();
ns.obj.setFoo("newfoo!");
ns.obj.printAry();
ns.obj.print();
I looked at this:
JavaScript array of pointers like in C++
But I'd like to be able to use an element of ary on the LHS of an assignment and I don't think that example works in this situation.
WHY ON EARTH DO I WANT TO DO THIS?
A lot of comments so far have (rightfully) asked why I'd want to do this. I'm dealing with a proprietary API that involves an asynchronous object initialization mechanism. Basically I create an instance of an object and then pass it to this initializer to be able to actually use it. The initializer includes a field for an onSuccess handler to notify of successful initialization. My fully initialized object is passed as an argument into this success handler so that I can grab a reference to it.
I'm then free to initialize my next object. It looks kinda like this:
var a = new api.ApiObject();
var b = new api.ApiObject();
var c = new api.ApiObject();
var d = new api.ApiObject();
//omg this is ugly
api.initializeObject( {
objToInit: a,
onSuccess: function(args) {
a = args.obj;
api.initializeObject( {
objToInit: b,
onSuccess: function(args) {
b = args.obj;
api.initializeObject( {
objToInit: c,
onSuccess: function(args) {
c = args.obj;
api.initializeObject( {
objToInit: d,
onSuccess: function(args) {
d = args.obj;
}
} );
}
} );
}
} );
}
} );
a.doCoolStuff();
//and so on
This deeply nested mess just gets worse as I add more api.ApiObjects(). So what do I do to fix this? I can't change the API, but maybe a recursive function could help:
//maybe a recursive function could make this more concise?
function doInitialize( ary ) {
api.initializeObject( {
objToInit: ary[0];
onSuccess: function(args) {
//i'd like to assign this passed in reference to my local
//reference outside this function (var a, b, etc).
//An array of pointers would be useful here.
//how else can I get this assigned out, cuz this doesn't work...
ary[0] = args.obj;
if( ary.length > 1 ) {
ary.splice( 0, 1 );
doInitialize( ary );
}
}
}
}
doInitialize( [a,b,c,d] );
//this won't work because I don't have a reference to the fully initialized object
a.doCoolStuff();
So maybe the better question is: is there an established pattern to deal with asynchronous success chaining like this? I think I've seen other public JS frameworks (like dojo) use this sort of onSuccess chaining... how do I make this not ugly?
I might suggest that if your primary purpose for this is convenience as regards nesting of asynchronous callbacks, that you should consider a deferred/promise system.
I've written a couple of different promise libraries by hand.
jQuery comes with one built in (as do most "ajax libraries").
Here's what this might look like, in a better world:
doThingOne()
.then(doThingTwo)
.then(doThingThree)
.then(launch);
Assuming that doThingOne returns a promise.
A more familiar looking interface for people who use jQuery (or most other promise-using large libraries), might look like this:
var imageLoader = $.Deferred(),
loading = imageLoader.promise();
loading
.done(gallery.render.bind(gallery))
.done(gallery.show.bind(gallery));
var img = new Image(),
url = "...";
img.onload = function () { imageLoader.resolve(img); };
img.onerror = function () { imageLoader.reject("error message"); };
img.src = url;
Very basically, the Deferred above will hold two private arrays (one for "success", one for "failure"), and will extend an interface which allows the async part of the application to "succeed" or "fail", and will pass in whatever is chosen to be data/a callback/etc.
It also extends a promise method, which returns a promise object, containing subscription functions for the two private arrays. So you pass the promise object around to interested parties, and they subscribe callbacks to be iterated through, on success/failure of the async operation (and passed anything which is passed to the .resolve/.reject method of the operation).
This might seem like an inversion or extension of just adding a custom-event/listener/etc...
And it is.
The benefit of the abstraction is that the interface is cleaner.
Hiding this stuff inside of object interfaces, and just passing async promise-objects around can make your code look 100% synchronous:
var images = ImageLoader(),
gallery = ImageGallery(),
photo;
photo = images.load("//url.com/image.png"); // assuming `.load` returns a promise object
gallery.show(photo); // just a promise object, but internally,
//`.show` would subscribe a private method to the promise object
And doing things like having three separate async operations, which can arrive in any order, but must all be successful before advancing, then you can have something like this (again jQuery, but doing it by hand is possible, too).
$.when(promise_obj_1, promise_obj_2, promise_obj_3)
.done(nextPhase);
nextPhase, of course, being a callback which you anticipate to be fired if all three promises are successfully completed.
I'd be happy to provide implementation details for a barebones promise system, if you're like me, and don't like using different libraries without first understanding how each piece works on its own, and being able to replicate its functionality, without copying code.
The answer to the first part of your question is to use an object. You're thinking in C which doesn't have iteratable structs so C programmers reach for arrays. In JS objects are iteratable. So you should write it as:
ary = {
foo : 'foo',
bar : 'bar'
}
Or if we look at your second example:
var apis = {
a : new api.ApiObject(),
b : new api.ApiObject(),
c : new api.ApiObject(),
d : new api.ApiObject()
}
Now, as for the second part of your question. Your pseudo recursive code (pseudo because it's not really recursive in the stack sense since it's async) will now work with the apis object above. But you pass the keys instead of the object:
doInitialize( ['a','b','c','d'] );
Obviously, the bit above can be done dynamically by iterating through the apis object. Anyway, in the onSuccess part of the code you assign the result like this:
apis[ary[0]] = args.obj;
Oh, and obviously the objToInit should now be apis[ary[0]].
Now doing this should work as you expect:
apis.a.doCoolStuff();

Categories

Resources