Giter VIP home page Giter VIP logo

streamlinejs's Introduction

streamline.js

streamline.js is a language tool to simplify asynchronous Javascript programming.

tldr; See Cheat Sheet

Instead of writing hairy code like:

function archiveOrders(date, cb) {
  db.connect(function(err, conn) {
    if (err) return cb(err);
    conn.query("select * from orders where date < ?", [date], function(err, orders) {
      if (err) return cb(err);
      helper.each(orders, function(order, next) {
        conn.execute("insert into archivedOrders ...", [order.id, ...], function(err) {
          if (err) return cb(err);
          conn.execute("delete from orders where id=?", [order.id], function(err) {
            if (err) return cb(err);
            next();
          });
        });
      }, function() {
        console.log("orders have been archived");
        cb();
      });
    });
  });
}

you write:

function archiveOrders(date, _) {
  var conn = db.connect(_);
  conn.query("select * from orders where date < ?", [date], _).forEach_(_, function(_, order) {
    conn.execute("insert into archivedOrders ...", [order.id, ...], _);
    conn.execute("delete from orders where id=?", [order.id], _);
  });
  console.log("orders have been archived");
}

and streamline transforms the code and takes care of the callbacks!

No control flow APIs to learn! You just have to follow a simple rule:

Replace all callbacks by an underscore and write your code as if all functions were synchronous.

Streamline is not limited to a subset of Javascript. You can use all the features of Javascript in your asynchronous code: conditionals, loops, try/catch/finally blocks, anonymous functions, chaining, this, etc.

Streamline also provides futures, and asynchronous variants of the EcmaScript 5 array functions (forEach, map, etc.).

News

The latest cool feature is TypeScript support. See https://github.com/Sage/streamlinejs/wiki/TypeScript-support for details.

Streamline 1.0 was a major revamp as a Babel Plugin. Streamline 2.0 was a smaller step from Babel 5 to Babel 6. See https://github.com/Sage/streamlinejs/wiki/Babel-upgrade for details.

Installation

NPM, of course:

npm install streamline -g

The -g option installs streamline globally.

You can also install it locally (without -g) but then the _node and _coffee commands will not be in your default PATH.

Note: If you encounter a permission error when installing on UNIX systems, you should retry with sudo.

Warning: you may get errors during install because fibers is now installed as an optional package and it may fail to build. But this package is optional and streamline itself should install fine.

Hello World

Streamline modules have ._js or ._coffee extensions and you run them with _node or _coffee.

Example:

$ cat > hello._js
console.log('hello ...');
setTimeout(_, 1000);
console.log('... world');
^D
$ _node hello

You can also create standalone shell utilities. See this example.

Compiling and writing loaders

You can also set up your code so that it can be run directly with node or coffee. You have two options here:

The first one is to compile your source. The recommanded way is with babel's CLI (see babel-plugin-streamline). But you can still use streamline's CLI (_node -c myfile._js or _coffee -c myfile._coffee)

The second one is to create a loader which will register require hooks for the ._js and ._coffee extensions. See this example.

Compiling will give you the fastest startup time because node will directly load the compiled *.js files but the registration API has a cache option which comes close.

The recommandation is to use the loader during development but deploy precompiled files.

Runtime dependencies

The runtime library is provided as a separate streamline-runtime package.

If you deploy precompiled files you only need streamline-runtime.

If your application/library uses a loader you will need to deploy both streamline-runtime and streamline with it.

Browser-side use

You have two options to use streamline in the browser:

  • You can transform and bundle your files with browserify. See how the build.js script builds the `test/browser/*-test.js files for an example.
  • You can also transform the code in the browser with the transform API. All the necessary JS code is available as a single lib/browser/transform.js file. See the streamlineMe example.

Generation options

Streamline can transform the code for several target runtimes:

  • callbacks. The transformed code will be pure ES5 code. It should be compatible with all JavaScript engines.
  • fibers. The transformed code will take advantage of the fibers library. This option is only available server-side.
  • generators. The transformed code will take advantage of JavaScript generators. It will run in node.js 0.12 (with the --harmony flag), in node.js 4.0 (without any special flag) and in latest browsers.
  • await. The transformed code will take advantage of ES7 async/await.

The choice of a target runtime should be driven by benchmarks:

  • The fibers mode gives superior development experience (because it uses real stacks for each fiber so you can step over async calls). It is also very efficient in production if your code traverses many layers of asynchronous calls.
  • The callbacks transform is obtained by chaining the generators transform and the regenerator transform. It is less efficent than the generators transform and we recommend that you use generators if generators are supported by your target JavaScript engine and that you only use callbacks if you target a legacy JavaScript engine.
  • The await mode is experimental at this stage. It relies on an emulation as async/await is not yet available natively in JavaScript engines.

You can control the target runtime with the --runtime (callbacks|fibers|generators|await) CLI option, or with the runtime API option.

Interoperability with standard node.js code

You can call standard node functions from streamline code. For example the fs.readFile function:

function lineCount(path, _) {
  return fs.readFile(path, "utf8", _).split('\n').length;
}

You can also call streamline functions as if they were standard node functions. For example, the lineCount function that we just defined above can be called as follows in standard node.js style:

lineCount("README.md", function(err, result) {
  if (err) return console.error("ERROR: " + err.message);
  console.log("README has " + result + " lines.");
});

You can mix streamline functions, classical callback based code and synchrononous functions in the same file.

Streamline only transforms the functions that have the special _ parameter.

Note: this works with all transformation options. Even if you use the fibers option, you can seamlessly call standard callback based node APIs and the asynchronous functions that you create with streamline have the standard node callback signature.

Interoperability with promises

Streamline also provides seamless interoperability with Promise libraries, in both directions.

First, you can consume promises from streamline code, by passing two underscores to their then method:

function myStreamlineFunction(p1, p2, _) {
  var result = functionReturningAPromise(p1, p2).then(_, _);
  // do something with result
}

Note: if the promise fails the error will be propagated as an exception and you can catch it with try/catch.

In the other direction you can get a promise from any callback-based asynchronous function by passing void _ instead of _. For example:

function readFileWithPromise(path) {
  var p = fs.readFile(path, 'utf8', void _);
  // p is a promise.
  p.then(function(result) {
    // do something with result
  }, function(err) {
    // handle error
  });
}

Futures

Streamline also provides futures. Futures are like promises, without all the bells and whistles. They let you parallelize I/O operations in a very simple manner.

If you pass !_ instead of _ when calling a streamline function, the function returns a future. The future is just a regular node.js asynchronous function that you can call later to obtain the result. Here is an example:

function countLines(path, _) {
  return fs.readFile(path, "utf8", _).split('\n').length;
}

function compareLineCounts(path1, path2, _) {
  // parallelize the two countLines operations
  var n1 = countLines(path1, !_);
  var n2 = countLines(path2, !_);
  // get the results and diff them
  return n1(_) - n2(_);
}

In this example, countLines is called twice with !_. These calls start the fs.readFile asynchronous operations and return immediately two futures (n1 and n2). The return statement retrieves the results with n1(_) and n2(_) calls and computes their difference.

See the futures wiki page for details.

The flows module contains utilities to deal with futures. For example flows.collect to wait on an array of futures and flows.funnel to limit the number of concurrent operations.

Asynchronous Array functions

Streamline extends the Array prototype with asynchronous variants of the EcmaScript 5 forEach, map, filter, reduce, ... functions. These asynchronous variants are postfixed with an underscore and they take an extra _ argument (their callback too), but they are otherwise similar to the standard ES5 functions. Here is an example with the map_ function:

function dirLines(dir, _) {
  return fs.readdir(dir, _).map_(_, function(_, file) {
    return fs.readFile(dir + '/' + file, 'utf8', _).split('\n').length;
  });
}

Parallelizing loops is easy: just pass the number of parallel operations as second argument to the call:

function dirLines(dir, _) {
  // process 8 files in parallel
  return fs.readdir(dir, _).map_(_, 8, function(_, file) {
    return fs.readFile(dir + '/' + file, 'utf8', _).split('\n').length;
  });
}

If you don't want to limit the level of parallelism, just pass -1.

See the documentation of the builtins module for details.

Exception Handling

Streamline lets you do your exception handling with the usual try/catch construct. The finally clause is also fully supported.

Streamline overrides the ex.stack getter to give you complete comprehensive stacktrace information. In callbacks and generators modes you get two stack traces:

  • the raw stack trace of the last callback.
  • the async stack trace of the asynchronous calls that caused the exception.

In fibers mode there is a single stack trace.

Exception handling also works with futures and promises. If a future throws an exception before you try to read its result, the exception is memorized by the future and you get it at the point where your try to read the future's result. For example:

try {
  var n1 = countLines(badPath, !_);
  var n2 = countLines(goodPath, !_);
  setTimeout(_, 1000); // n1 fails, exception is memorized
  return n1(_) - n2(_); // exception is thrown by n1(_) expression.
} catch (ex) {
  console.error(ex.stack); // exception caught here
}

Special callbacks

multiple results

Some APIs return several results through their callback. For example:

request(options, function(err, response, body) {
  // ...
});

You can get all the results by passing [_] instead of _:

var results = request(options, [_]);
// will be better with destructuring assignment.
var response = results[0];
var body = results[1];

Note: if you only need the first result you can pass _:

var response = request(options, _);

callback + errback

Some APIs don't follow the standard error first callback convention of node.js. Instead, the accept a pair of callback and errback arguments. Streamline lets you call them by passing two _ arguments. For example:

function nodeStyleFn(arg, _) {
  return callbackErrbackStyleFn(arg, _, _);
}

As seen above, this feature is used in the promise interop: result = promise.then(_, _) is just a special case.

It can also be used to handle the special error-less callback of fs.exists:

function fileExists(path, _) {
  // the second _ is ignored by fs.exists!
  return fs.exists(path, _, _);
}

CoffeeScript support

CoffeeScript is fully supported.

Debugging with source maps

You can seamlessly debug streamline code thanks to JavaScript source maps. See this video for a quick demo.

To activate this feature, pass the --source-map options to _node or _coffee, or set the sourceMap option if you register via a loader.

Examples

The tutorial shows streamline.js in action on a simple search aggregator application.

The diskUsage examples show an asynchronous directory traversal that computes disk usage.

The loader examples demonstrate how you can enable the ._js and ._coffee require hooks.

Online demo

You can see how streamline transforms the code by playing with the online demo.

Troubleshooting

Read the FAQ.

If you don't find your answer in the FAQ, post to the mailing list, or file an issue in GitHub's issue tracking.

Related Packages

The following packages are installed together with streamline:

The following packages extend the power of streamline:

Resources

The tutorial and FAQ are must-reads for starters.

The API is documented here.

For support and discussion, please join the streamline.js mailing list.

Credits

See the AUTHORS file.

Special thanks to Marcel Laverdet who contributed the fibers implementation and to Geoffry Song who contributed source map support (in 0.x versions).

License

MIT

streamlinejs's People

Contributors

abiyani avatar aikar avatar amilajack avatar anodos avatar anton-evseev avatar aseemk avatar bcko avatar bdowning avatar benjiwheeler avatar bjouhier avatar bpartridge avatar calvinwiebe avatar coolwanglu avatar dependabot[bot] avatar eboureau avatar felixrabe avatar flatheadmill avatar goffrie avatar laverdet avatar mlin avatar mreinstein avatar pguillory avatar sethyuan avatar spollack avatar willconant avatar winniehell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

streamlinejs's Issues

Invalid syntax when moving a var declaration out of for loop setup

The following program

function main(_) { 
  for(var x=0; x<2; x++); 
  something(_)
  for(var x=0; x<2; x++);
} 

gets transformed by streamline into

/*     1 */ main = function main(_) {
              if (!_) {
                return __future(main, arguments, 0);
              }
            ;
              var __then = _;
              var x;
/*     2 */   for (x = 0;; (x < 2); x++) {
              ;
              };
/*     3 */   return something(__cb(_, function() {
/*     4 */     for (x = 0;; (x < 2); x++) {
                ;
                };
                return __then();
              }));
            };

which is of course a syntax error. I can fix the problem by making the assignment template used for this transformation an "expression" (see the patch below), but I'm not yet familiar enough with the internals of streamline to be confident that is the right thing to do.

diff -r e71538d63510 -r e7c2ca85bf10 libraries/streamline/compiler/transform.js
--- a/libraries/streamline/compiler/transform.js    Sat Jun 18 18:22:49 2011 -0400
+++ b/libraries/streamline/compiler/transform.js    Sun Jun 19 08:43:06 2011 -0400
@@ -423,7 +423,7 @@

    var _assignTemplate = new Template( function _t() {
        lhs = rhs;
-   });
+   }, true);
    function _fixScopeVar(node) {
        var idents = node._scope.identifiers;
        var hasOut = node.children.some( function(ident) {

Doesn't deal with top-level asynchronous calls

I'm playing with persistence.js and streamlinejs a bit:

var persistence = require('persistencejs').persistence,
    store       = require('persistencejs').store.mysql;

store.config(persistence, 'localhost', 3306, 'persistence', 'persistence', 'persistence');

var Task = persistence.define('Task', {
    name: "VARCHAR(200)"
});

var session = store.getSession();

session.syncSchema(_);

var t = new Task(session, {name: "My new task"});
session.add(t);
session.flush(_);

This fails, because of the top-level statements that don't appear to translate well. I have to put everything in a function. Not a big deal, but also, well, a shame :-)

Don't require output file to be present?

I've already emailed this but I figure we should properly track it in the issue tracker. =) You mentioned that Node v0.3 onwards passes the full filename to you, so this should hopefully be possible now. That'd be great!

streamline.js removes quotes around object literal keys, which can result in incorrect code

var foo = {'10k':'baz'};

gets compiled into

/*** Generated by streamline 0.1.37 - DO NOT EDIT ***/
var __g=typeof global!=='undefined'?global:window;__g=(__g.__streamline||(__g.__streamline={}));__g.setEF=__g.setEF||function(e,f){e.__frame = e.__frame||f};__g.cbTick = 0;var __nextTick=(typeof process!='undefined'&&typeof process.nextTick=='function')?process.nextTick:function(fn){setTimeout(function(){fn()},0);};var __srcName='test_.js';
/*     1 */ var foo = {
/*     1 */   10k: "baz"
            };

which yields

frew@frewdesk:~/ff/littlefinger$ node-streamline test_.js 

/home/frew/ff/littlefinger/test_.js:1
o = { 10k: "baz"
      ^^
Unexpected token ILLEGAL

Don't output generated file to disk if input explicitly specifies _

The title is what I think I'd both want and would be a reasonable default (as opposed to having to specify an option either on the command-line or in a file, for every single invocation or within every single file). And since support for explicitly specifying _ is new, it should hopefully not break anyone. ;)

Here are some command-line test cases that hopefully make this more clear:

$ node-streamline foo_.js
# don't save foo.js to disk if it doesn't already exist,
# but go ahead update it if it does

$ node-streamline foo_
# same as for foo_.js

$ node-streamline foo.js
# since this will only work if foo.js already exists,
# go ahead update it on disk

$ node-streamline foo
# same as for foo.js

And the same from JavaScript:

require('./foo_.js')
require('./foo_')
require('./foo.js')
require('./foo')

Thanks so much again for all your support. I'm seriously in love with Streamline. I hope to be able to repay you with some contributions in the future!

Wrong order of precedence w/ comma operator

This could maybe be considered another bug due to the quirky JS that CoffeeScript generates, but it seems like a legitimate bug: Streamline doesn't seem to be using the right operator precedence / order of operations when there's a comma operator being used.

Here's an example in CoffeeScript:

thing =
  if id = input.id
    Thing.getById id, _
  else
    url = adjustURL input.url
    Thing.getByURL url, _

Note the else clause in particular: I want to execute a statement that assigns the url variable, then pass that variable into Thing.getByURL(...).

Here's the JavaScript that gets generated:

var id, thing, url;
thing = (id = input.id) ? Thing.getById(id, _) : (url = adjustURL(input.url), Thing.getByURL(url, _));

Note the use of the comma operator: url = ..., Thing.getByURL(url, _). In every JS engine, this works as expected: the comma has the lowest precedence (the left side gets evaluated, then the right side).

Streamline unfortunately generates wrong JS:

(function main(_) {
  var id, thing, url;
  var __frame = {
    name: "main",
    line: 1
  };
  return __func(_, this, arguments, main, 0, __frame, function __$main() {
    return (function __$main(_) {
      var __1 = (id = input.id);
      return (function __$main(__then) {
        if (__1) {
          return Thing.getById(id, __cb(_, __frame, 1, 26, _));
        }
         else {
          __then();
        }
      ;
      })(function __$main() {
        return Thing.getByURL(url, __cb(_, __frame, 1, 78, function ___(__0, __4) {
          var __3 = (url = adjustURL(input.url), __4);
          return _(null, __3);
        }));
      });
    })(__cb(_, __frame, 7, 6, function ___(__0, __2) {
      thing = __2;
      _();
    }));
  });
}).call(this, __trap);

Note how Thing.getByURL(url, ...) is called before the url = ... assignment. Streamline thinks I need to evaluate this value before I can use it in the comma-separated expression url = ..., Thing.getByURL(...).

Hope this makes sense and is fixable. Let me know if I can provide any more info. Thanks!

Nested ifs and/or switches cause bugs

This past weekend, I ran into a nasty Streamline bug where nested switch statements seemed to mess up the synchronous flow of our Streamline code. (It took me a really long time to find this out, because it broke basic assumptions I was making about the code working.)

Fortunately, I was able to work around it easily by switching to nested ifs. The code seemed to work properly after that point, so I didn't bother making a simplified repro, etc. and instead just put it on a to-do list for the future.

But today, I've run into another crazy bug where code again isn't getting executed in what seems to me should be the right manner. I sent an email to the group about it, with a link to the gist, but unlike the switch bug, the symptom is a bit different -- the end of a method gets executed twice -- and I haven't been able to find any simple workaround.

http://groups.google.com/group/streamlinejs/browse_thread/thread/20d846ca64f500a8

I figured I'd take the time now to properly investigate the switch bug, and to my surprise, my simplified repro of this switch bug demonstrates the same executed-twice bug (in addition to the no-longer-sync bug I was seeing earlier). I thus figured these may both be due to the same root cause, so I'm filing this combined issue.

Here's my simple Coffee-Streamline source which has nested switch statements:

https://gist.github.com/1135758#file_switches_.coffee

Each async step is "sync" in Streamline style, so you would expect output like:

BEGIN
fetching user...
user fetched.
fetching target...
target fetched.
END

But instead, what you get is ( https://gist.github.com/1135758#file_switches_output.txt ):

BEGIN
fetching user...
END
user fetched.
fetching target...
target fetched.
END

Note how not only do the sync steps get executed asynchronously, the end of the method gets executed twice.

Here are the intermediate JS files for this Coffee-Streamline source:

https://gist.github.com/1135758#file_switches_.js
https://gist.github.com/1135758#file_switches.js

When I switch to nested ifs instead of nested switches, this simplified example works as expected:

https://gist.github.com/1135758#file_ifs_.coffee
https://gist.github.com/1135758#file_ifs_.js
https://gist.github.com/1135758#file_ifs.js
https://gist.github.com/1135758#file_ifs_output.txt

I hope this information helps, and thanks in advance Bruno for looking into this. Let me know if I can provide anything else.

Error from Narcissus: Node.prototype.length is gone

This one is just brutal. I've spent hours debugging this to no avail.

First, here's the error we get:

Error: error streamlining source: Node.prototype.length is gone; use n.children.length instead
    at Object.transform (/usr/local/lib/node/.npm/streamline/0.1.25/package/lib/compiler/transform.js:1588:10)
    at Object..coffee (/usr/local/lib/node/.npm/streamline/0.1.25/package/lib/compiler/register.js:97:29)
    at Module.load (module.js:336:31)
    at Function._load (module.js:297:12)
    at require (module.js:348:19)
    at Object.<anonymous> (/Users/aseemk/Projects/Foo/www/app.coffee:7:13)
    at Object.<anonymous> (/Users/aseemk/Projects/Foo/www/app.coffee:113:4)
    at Module._compile (module.js:404:26)
    at /usr/local/lib/node/.npm/coffee-script/1.1.1/package/lib/coffee-script.js:14:21
    at Object..coffee (/usr/local/lib/node/.npm/streamline/0.1.25/package/lib/compiler/register.js:68:11)

This happens when we try to run node app.js, where app.js is just this:

require('coffee-script');
require('streamline');

require('./app.coffee');

And app.coffee starts with:

# ...a couple of other requires...
require './foo_'
require './bar_'
# ...more stuff...

The error occurs at the require './bar_' call. That file is bar_.coffee, written in Streamline syntax. But here's the crazy thing: _compiling bar_.coffee by itself works just fine_.

It gets weirder: if we comment out the previous require (foo_.coffee), the whole thing compiles fine. And foo_.coffee compiles fine on its own too.

At a high level, this suggests that the compiler (or Narcissus, or something) is somehow maintaining state between compilations, such that bar_.coffee's compilation depends on foo_.coffee's compilation.

Does this ring any bells or give you any ideas for what the problem might be? If not, more details follow.

I mentioned foo_.coffee compiles fine by itself. Within it, it requires another module, say module, which is also written in CoffeeScript+Streamline. If we comment out require 'module' in foo_.coffee, then bar_.coffee works again. Weird, right?

But this doesn't change the high-level symptom: that requiring/compiling one module/file is somehow affecting whether another module/file successfully compiles.

I spent some time git bisecting to find which code change I had made started causing this. I narrowed down the culprit to a third-party module: node-xml2js. This module extends Object.prototype with a forEach method: proto.js.

Note though that it does so intelligently, using Object.defineProperty() and a data descriptor which defaults to enumerable: false (documentation).

All I know, though, is that if I comment out that extension, everything compiles fine. If I leave it as is, it doesn't. I've spent a ton of time trying to instrument and trace why this might be happening, but I can't get any insight. (Using a data descriptor doesn't let me instrument when the field is accessed or set; using an accessor descriptor lets me instrument that, but it changes the semantics and changes the behavior of the program.)

Any ideas why extending Object.prototype (which, believe me, I know is a bad practice) might be affecting Streamline too?

In the process of writing this post, I discovered that xml2js has actually changed its behavior and no longer extends Object.prototype! The update is just not on npm yet, so I've asked the author for that. Either way, it's awesome and it indeed fixes this horrible error for me, but I thought you might still find this information valuable?

Phew. Hope this helps sometime. Cheers.

Support func.apply(this, arguments)

It would be great to support calling an async function "synchronously" using Function::apply(), passing in the arguments variable which contains the Streamline (underscore) callback parameter.

I whipped up a test case for this:

https://gist.github.com/1138411

Since Streamline doesn't detect the calling the inner function should "wait", the script currently fails with output of something like this:

PASS: `this` was properly preserved.
PASS: `this` was properly preserved.
FAIL: outer exiting before inner.
FAIL: argument was not properly passed: [object Object]
FAIL: inner exiting after outer.

You can work around the lack of this support using Function::call(), since that lists the underscore explicitly. (Thanks for that tip!) It then passes as expected:

PASS: `this` was properly preserved.
PASS: `this` was properly preserved.
PASS: inner exiting before outer.
PASS: outer exiting after inner.
PASS: argument was properly preserved.

In other cases, I've seen behavior where the last part of the code gets executed multiples times, since the inner isn't getting "waited" upon by Streamline, but the same callback parameter is passed to it, but interestingly, I'm not seeing that behavior here. Not sure why.

Thanks for all the help, and thanks in advance for looking into this!

Streamlining functions w/o needing result

something I keep running into is wanting to use streamline in a function where I don't care to ever get a callback with its final results

for example:

function fileAppend(file, data) {
(function(_) {
var id = fs.open(file, 'a+', 0666, _);
fs.write(id, data, null, 'utf8', _);
fs.close(id, _);
fs.chmod(file, 0666, _);
})(function(err) { if (err) throw err; });
}

I don't need to know when its done, but I'm required to put this wrapper around the code or else streamline complains that im using async code in a function that doesnt have a _ parameter.

But in this case I don't care for the response, I just want to write

fileAppend('/tmp/foo', 'bar'); and be done with it.

So i'd like to see a way to handle this situation easier, so you don't need to stub around functions like that.

maybe stupid question

Since my other comment turned out to be stupid I should be careful now. So I'm asking this question before coding.

I switched to async.js for one function during the period of time I gave up on streamline. That library didn't work because when I used a loop iterator ("until"), after a 1000 or so loops node threw a stack-overflow error. That made me realize the library was doing a recursive call each time around the loop. This is obviously not acceptable.

Does streamline do the same thing? I never did read your Harry story closely.

Actually, how do I do an async loop using the node callbacks without streamline? I am a relative newbie to the whole pure async thing and I'm a bit stumped on this. If you wish I can ask this on nodejs group.

wrong arg order in example?

Shouldn't this have the callback first and p second?

function processFiles() {
    // ...
    fileLength(p, function(err, len) { // OK
        // ...
    });
}   

Cannot find module, after npm install

Maybe it's something with my node/npm setup (other modules work fine, though), but after I install streamline using:

npm install streamline

and run it from the command line:

node-streamline ...

I keep getting the following error:

node.js:116
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error: Cannot find module 'streamline'
    at Function._resolveFilename (module.js:290:11)
    at Function._load (module.js:242:25)
    at require (module.js:318:19)
    at Object.<anonymous> (/Users/zef/.node_libraries/.npm/streamline/0.1.1g/package/bin/node-streamline:3:1)
    at Module._compile (module.js:374:26)
    at Object..js (module.js:380:10)
    at Module.load (module.js:306:31)
    at Function._load (module.js:272:10)
    at Array.<anonymous> (module.js:393:10)
    at EventEmitter._tickCallback (node.js:108:26)

I'm using node 0.4.1 with npm 0.3.9.

Bug if last case has no break

If the very last case in a switch statement has no break, and it contains async calls, Streamline never continues beyond the switch statement. Here's an example / test case:

https://gist.github.com/1085874

I'm aware that fall-through cases are explicitly not supported by Streamline:

https://github.com/Sage/streamlinejs/wiki/Limitations

But, for context, we came across this bug through CoffeeScript, which strips the last case's break if there's no default, as an (albeit minor) optimization.

The workaround is easy on our end -- we can just add an explicit break or default -- but I wanted to let you know about this as another minor gotcha we've run into using CoffeeScript and Streamline together.

If it's too difficult to account for this, it's understandable. But if it's easy enough to support this, like consecutive case statements with no code in between them, that'd be awesome. =)

Thanks in advance!

P.S. It struck me that Streamline always has zero open issues and a ton of closed ones. That's impressive -- Streamline is a model library and you are a model maintainer. Thanks much for your contributions!

/cc @gasi

Loop invalid syntax bug! =P

This one has haunted me on and off for quite a while, but every time in large codebases where I didn't know exactly what the problem was and didn't have time to whittle it down to a root cause. But now, finally, I have!

Here's a simple test case:

https://gist.github.com/957996

As the description says, I think this has to do with where the var declaration is. If I move the var val declaration directly inside the for loop, it works. Otherwise, as it is in that gist, the output results in invalid syntax -- var = __1[__2++]; -- inside the loop.

I'm glad to have finally found the bug! I hope fixing it isn't too difficult. =) Good luck, and thanks in advance!

non-standard callbacks

I decided to try streamline on one of my production files. The first async func I ran into was child_process.exec which has a callback with params that look like (error, stdout, stderr). Can streamline handle this?

errors & try catch woes

Take this code

function test() {
    try {
    console.log('test');
    (function(_) {
        foo.bar();
    })(function(err) {
        if (err) throw err
    });
    } catch (e) {
        console.error("E:",e);
    }
}
test();

if ran through node, I get "E: { stack... type: 'not defined'" as expected

Run through streamline, and I get an uncaught exception, w/o my E: prefix

then without the function error stub doing it the way you told me I can do with leaving off the function entirely:

function test() {
    try {
    console.log('test');
    (function(_) {
        foo.bar();
    })();
    } catch (e) {
        console.error("E:",e);
    }
}
test();

No error is printed at all. The error is lost!

same without the try catch

function test() {
    console.log('test');
    (function(_) {
        foo.bar();
    })();
}
test();

Imo id consider this a bug. Id at least like to see the errors, but ideally the parent try catch should of caught it.

This isnt even async code.

Return statement required

A streamlined function without a return statement throws the error ReferenceError: __ is not defined. Normally functions implicitly return undefined.

Example:
//!!STREAMLINE!!

f(function(err) {});

function f_() {
    //return null;
}

Error thrown in callback gets caught

If an error is thrown inside a streamlined function's callback, it calls the callback again with an error. That's got to be incorrect. The callback should only be called once, either with an error or a result. If a callback throws, that error should not be caught by the streamlined function.

Example:
var Streamline = require('..');

function f_() {
    return 5;
}

eval(Streamline.transform(f_.toString()));

f(function(err, result) {
    console.log('err: ' + err + ', result: ' + result);
    throw new Error();
})

Expected result:
err: null, result: 5

node.js:116
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error
    [...stack trace...]

Actual result:
err: null, result: 5
err: Error, result: undefined

node.js:116
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error
    [...stack trace...]

Two calls, first with the result, then again with an error.

The streamlined function looks like this:
function f() {
var __ = (
= (_ || __throw));
try {
return _(null, 5);
} catch (__err) {
return _(__err);
};
};

That try/catch block doesn't belong there. Function calls from the streamlined function should be wrapped, but the callback call with the result (corresponding to the streamlined return statement) should not.

Narcissus parses many number literals incorrectly!

Any number literal that begins with "0." or "." or "-0." or "-." is parsed by Narcissus.parser.parse, and therefore by streamline, to have a value equal to its offset in the input string rather than its actual value.

Literally the first program I tried to transform with streamline hit this problem, changing the number 0.01 to 172. I was pretty baffled at first :-)

I have submitted a patch for this problem upstream at https://bugzilla.mozilla.org/show_bug.cgi?id=665292, but I thought I should mention it here since it can severely break streamline's output. You might want to apply my patch to your repository and/or help get it adopted upstream.

Change Request: New Runtime loader

Hi bruno, I've got a better way to implement the runtime loader and have got it working in a branch.

https://github.com/aikar/streamlinejs/tree/newruntimeloader
(it also contains the commit to fix #49)

  1. overload the _compile module prototype instead of overloading the extension.
  • This avoids conflicts with other scripts also overloading the extension. Everything HAS to call the original _compile to get a module to load as expected unless they completely implement their own module loader.
  • Gets rid of all the coffeescript code in register.js. .coffee handler will be calling ._compile with the translated coffee code for you, and so will ANY other language preprocessor. so out of the box better support for other extensions
    2) add a "compileAnyways" flag when 'running' a script from the node-streamline script
    • if the file passed does not have a _.js version of itself (or not calling a _.js file), the original code simply returned the contents unmodified. I've added this compileAnyways flag so that you can now quickly write shell scripts with it without using _.js name format.
  • for example: ~/bin/foo has contents:
    #!/usr/bin/env node-streamline
    console.log(require('fs').readFile('/etc/passwd', _).toString())

Then executing foo would still work as expected with streamline.

This makes using the _.js format not required when using node-streamline.

Now. This shouldn't be pulled into master... I did this up roughly so you can see what I propose it should behave as.

I don't know the system well enough to do proper testing of the changes and writing proper tests for it, but still want to give you the code so you can take it from here or drop it (hopefully not...)

More useful anonymous function names

We've noticed that when Streamline encounters unnamed anonymous functions, it makes them named. This is to support futures, since ES5 strict mode disallows arguments.callee.

This makes sense, but unfortunately, this makes stack traces a lot harder to read. This is because all (modern, at least?) JS engines show the calling variable's/property's name if a function is unnamed, but Streamline gives the functions names, so the engines use these names, but the names are just meaningless numbers (e.g. __6).

It would thus be very helpful if the names Streamline generates, while still unique, were derived from the name of the variables/properties that the functions are assigned to, for those functions that are indeed assigned to variables/properties. Here are some examples:

var foo, Utils, Comment;

// before:
foo = function () { ... }
// after e.g.:
foo = function foo__6 () { ... }

// before:
Utils.helper = function () { ... }
// after e.g.:
Utils.helper = function Utils_helper__6() { ... }

// before:
Comment.prototype['delete'] = function () { ... }
// after e.g.:
Comment.prototype['delete'] = function Comment_prototype_delete__6 () { ... }
// or maybe e.g.:
Comment.prototype['delete'] = function Comment$delete__6 () { ... }

This is obviously low priority, just a "nice to have" feature request, but hopefully this is doable sometime! Thanks. =)

Support `node-streamline foo_`

I really appreciate the change you made to no longer require foo.js to be present for my Streamlline module foo.js_. At the risk of being a nuisance ;), I do have an extension to that request:

Now, as expected, node-streamline foo no longer works since foo.js doesn't exist, so I would have expected node-streamline foo_ to work, but it doesn't either. Would it be possible to support that too? =)

As always, if you're open to the idea but would like to offload the work of implementing it, just let me know. Thanks for your consideration!

Cheers,
Aseem

Minor: function properties w/ special chars in name fail to generate valid JS now

E.g. this source:

exports['search works?'] = function(beforeExit, _) {};

Generates this JavaScript:

exports["search works?"] = function exports_search works?__1(beforeExit, _) {
  var __frame = {
    name: "exports_search works?__1",
    line: 1
  };
  return __func(_, this, arguments, exports_search works?__1, 1, __frame, _);
};

Note the invalid space and question mark in the generated function name exports_search works?__1. This means the JS doesn't compile.

FYI! I only ran into this because this was an Expresso test case, where the convention is to use long, descriptive names for the test cases, and those names happen to be properties on the exports object.

SyntaxError: module statements only allowed in Harmony

demo.js:
//!!STREAMLINE!!
module.exports = {}

Demo:
# node ./streamlinejs/lib/node-init.js demo.js

node.js:116
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
SyntaxError: module statements only allowed in Harmony
    at Object.newSyntaxError (/Users/pguillory/projects/MinecraftAdmin/deps/narcissus/lib/jslex.js:448:21)
    at Statement (/Users/pguillory/projects/MinecraftAdmin/deps/narcissus/lib/jsparse.js:437:21)
    at Statements (/Users/pguillory/projects/MinecraftAdmin/deps/narcissus/lib/jsparse.js:314:24)
    at Script (/Users/pguillory/projects/MinecraftAdmin/deps/narcissus/lib/jsparse.js:170:23)
    at parse (/Users/pguillory/projects/MinecraftAdmin/deps/narcissus/lib/jsparse.js:1717:17)
    at /Users/pguillory/projects/MinecraftAdmin/deps/streamlinejs/lib/transform.js:1178:14
    at streamline_file (/Users/pguillory/projects/MinecraftAdmin/deps/streamlinejs/lib/node-init.js:70:21)
    at Object..js (/Users/pguillory/projects/MinecraftAdmin/deps/streamlinejs/lib/node-init.js:36:17)
    at Module.load (module.js:234:31)
    at Function._load (module.js:201:10)

It appears to have started with Narcissus commit 24415c6f1e354865c02b on 2011-02-10.

for(;;) not working

Following code doesn't go through streamlineing.

I know its coded complicated, its just a minimal example. while(true) works too, but any form of for(;;) I tried doesn't work.

function f(_) {
    var j = 0;
    for(;;) {
        process.nextTick(_);
        j++;
        console.log("hallo");
        if (j === 100) break;
    }
}

f(function(){});

Change Request: Move common runtime stub to its own module.

I don't know enough of the internals to even take a crack at this myself.

But ideally I think the common runtime stub added to every compiled file could be moved to its own shared module.

similar to what I did with Traceur:
http://github.com/aikar/traceur

Some parts I understand cant be shared, but imo the shared parts could be moved

Some advantages:

  1. its easier to manage than a bunch of strings in transform.js
  2. shared so V8 can compile and optimize it better, so hopefully a performance benefit.
  3. reduced duplication of code, ie reduced memory.

So ideally, the top of each source file would be like so
var __streamline = require('streamline-runtime')('path.to.source');

but I'm really wondering do we need the filename stored in a variable though, cant it just report the active file?

ie require('streamline-runtime')(__filename);

Silent compiler failures

I've repeatedly run into the case that node-streamline -c <file> fails silently on some inputs. That is, it doesn't provide any output, giving the false impression that it succeeded, when in fact no output file was generated.

I haven't been able to confirm that this is always the case, but when I ran into this just now, I plugged the source into the online demo, which shows that there was an error in my source.

The bug I'm filing is that the compiler should never fail silently: it should be outputting some error message.

Here's a simple test case I just whipped up, simplified from the case I just ran into:

https://gist.github.com/956617

If you plug that into the online demo, you see an error message. But if you run node-streamline -c on it, it fails silently.

I'm not well-versed in the Streamline code enough to be confident where to make this change, but some quick logging output showed that it indeed just fails silently within transform(). Hope that helps!

Sync stack traces: undefined filenames

Hey there Bruno,

Great work on the sync stack traces so far! It's very exciting to have readable stack traces that finally make sense.

We however noticed that the filenames in our stack traces are always undefined, e.g.

Error: 400
    at ClientRequest.<anonymous> (/usr/local/lib/node/.npm/jsonreq/0.1.0/package/jsonreq.js:21:29)
    at ClientRequest.g (events.js:143:14)
    at ClientRequest.emit (events.js:64:17)
    at HTTPParser.onIncoming (http.js:1335:9)
    at HTTPParser.onHeadersComplete (http.js:108:31)
    at Socket.ondata (http.js:1212:22)
    at Socket._onReadable (net.js:677:27)
    at IOWatcher.onReadable [as callback] (net.js:177:10)
    at exports_getItemInfo__1 (undefined_.js:8:11)
    at Thing_getOrCreateFromSource__10 (undefined_.js:178:20)
    at exports_search__7 (undefined_.js:171:14)
    at __1 (undefined_.js:24:20)

(The calls at the bottom are Streamlined; the jsonreq package unfortunately isn't -- yet -- hence the non-sync lines above.)

The way we use Streamline is fully dynamically -- we require() Streamline, we only have foo_.coffee files lying around, and we simply require('foo_') to generate the compiled files at runtime.

This isn't a huge deal -- we can generally figure out from the method names (big win, btw) what file a call is referring to -- but it would be nice to help debugging.

Thanks in advance!

this-value is incorrect in certain situations

I think I have found an issue with the transformation process that can lead to the value of this being incorrect in certain situations. Here is an example that works correctly. If you paste it into the streamline demo and execute the code, you'll get an alert with the message "this is the stuff."

function foo(_) {
  if (this.needsBar) {
    this.stuff = "this is the stuff";
    this.bar(_);
  }
  return this.stuff;
}

function bar(_) {
  this.needsBar = false;
}

var x = { foo: foo, needsBar: true, bar: bar };

alert(x.foo(_))

Now, here is that same example with lines 3 and 4 flipped, which leads to line 6 being broken:

function foo(_) {
  if (this.needsBar) {
    this.bar(_);
    this.stuff = "this is the stuff";
  }
  return this.stuff; // now this refers to the wrong value
}

function bar(_) {
  this.needsBar = false;
}

var x = { foo: foo, needsBar: true, bar: bar };

alert(x.foo(_))

If you look at the transformed code, in the working version, the if-block is terminated with:

return this.bar(__cb(_, this, __));

Which wraps the continuation such that the correct value of this is preserved. In the broken version, however, the callback passed to this.bar(_) is simply terminated with:

return __();

Which doesn't correctly preserve the value of this.

require() coffeescript?

Hey there,

I have yet another feature request! ;) Thanks in advance, and as always, if you like the idea but don't have time to implement it and would like me to, just let me know.

(And as always, I'm making this request only because I love Streamline and want to be able to use it anywhere!)

I've just started using CoffeeScript, which is intriguing and nice, but -- just like regular JavaScript -- the vast majority of the files in our app are require()-ed rather than run from the command-line. Right now, it seems Streamline supports CoffeeScript only via the coffee-streamline command-line wrapper.

I can imagine this isn't a trivial problem, but do you have any thoughts on supporting require() with CoffeeScript files?

Here is what I'd imagine in an ideal world, e.g. from a JS file, importing a Coffee file in Streamline syntax:

require('coffee');
require('streamline');
require('./foo_')    // i can live with './foo' for now! ;)

I've noticed that require.extensions supports only one function per extension. Perhaps you can detect that if .coffee already has a registered handler, wrap it in your own that calls the inner one and then streamlines it?

Cheers,
Aseem

Synch functions throw through streamlined functions

A streamlined function can throw an error by calling a normal function containing a throw statement. It seems like errors should be redirected through the callback, since that's what happens to throw statements inside the streamlined function.

One workaround is to wrap all calls from streamlined to non-streamlined functions in try-catch blocks that rethrow the error, causing it to be redirected through the callback. That violates the principle of least surprise, though -- catching and rethrowing an error should have no effect, semantically.

It seems like it could be fixed by having the transformation engine do that try-catch wrapping automatically.

Here is the current behavior.

//!!STREAMLINE!!
var assert = require('assert')

assert.throws(function() {
    asynch_func(function(err) {})
})
assert.doesNotThrow(function() {
    asynch_func_with_try(function(err) {})
})

function asynch_func(_) {
    synch_func()
}

function asynch_func_with_try(_) {
    try {
        synch_func()
    } catch (err) {
        throw err
    }
}

function synch_func() {
    throw new Error('test error')
}

Optional callbacks

This might be considered a feature request.

Sometimes I want to call a streamlined function with setTimeout() or process.nextTick().

Example:
function f_() {
return null
}
process.nextTick(f)

Which becomes:
function f(_) {
return _(null, null);
};
process.nextTick(f);

function __cb(_, fn){
    return function(err, result){
        if (err) 
            return _(err);
        try {
            return fn(result);
        } 
        catch (ex) {
            return _(ex)
        }
    }
}

That throws a TypeError: undefined is not a function, because nextTick() didn't pass in the callback parameter. It would be cool if streamlined functions checked to see if a callback was passed, and if not, either do nothing on success, or throw in case of an error.

Something like this:
function f(_) {
_ = _ || __throw
return _(null, null);
};
process.nextTick(f);

function __cb(_, fn){
    return function(err, result){
        if (err) 
            return _(err);
        try {
            return fn(result);
        } 
        catch (ex) {
            return _(ex)
        }
    }
}

function __throw(err) {
    if (err) throw err
}

Streamline doesn't generate __cb in one case where it's needed

function test(i, _) {
  console.log("" + i)
}

var i = 0;
while (i < 101) {
  test(i);
  i++;
}

yields

[elided output]
96
97
98

node.js:134
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
ReferenceError: __cb is not defined

since the nextTick code tries to use __cb, but the function is only generated if a sync call is made.

This is a nitpicky rather than practical bug report. :)

using coffeescript now

Is there any reason I cannot use streamline with coffeescript now? I already have my tool to compile coffeescript files just-in-time and it would be easy to call the streamline compiler every time right after. Coffee does produce legal js.

transform.js can't be minified

I'm deploying streamline.js in a browser environment. I've noticed that I can't run transform.js through my normal minification tool (UglifyJS) because Template is using Function.toString(). Uglify will mangle the names in the template function so that they are useless to Template. The only way I can think of to fix this issue is to convert all the templates to strings in the code, which makes streamline's code a little less pretty, but would have the side benefit of making the generated code more reliably consistent across environments. Thoughts?

Conflict with underscore.js

Like the others, we already have a thread on this but figured it would be good to formally track this.

The solution to this may end up being adding support for the callback name to be configurable (from the default _), but that's more of a means to me rather than the end goal: I'd just love to be able to use underscore.js with streamline. =)

using streamline as a shebang wrong file path

say you have ~/bin/x.js

and at top you have #!/usr/bin/env node-streamline

and you issue it from /home/aikar

node-streamline tries to load /home/aikar/home/aikar/bin/x.js

this is due to using path.join instead of path.resolve

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.