• Stars
    star
    6,026
  • Rank 6,641 (Top 0.2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 11 years ago
  • Updated about 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Like Underscore, but lazier

Like Underscore, but lazier

Build Status Bower version NPM version

Lazy.js is a functional utility library for JavaScript, similar to Underscore and Lodash, but with a lazy engine under the hood that strives to do as little work as possible while being as flexible as possible.

It has no external dependencies, so you can get started right away with:

npm install lazy.js

(Note the package is called "lazy.js", with a dot.)

Or, if you're using Lazy.js in the browser:

<script type="text/javascript" src="lazy.js"></script>

<!-- optional: if you want support for DOM event and AJAX-based sequences: -->
<script type="text/javascript" src="lazy.browser.js"></script>

Now let's look at what you can do with Lazy.js. (For more thorough information, take a look at the API Docs.)

Introduction

Let's start with an array of objects representing people.

var people = getBigArrayOfPeople();

Suppose we're using this array to back some sort of search-as-you-type functionality, where users can search for people by their last names. Naturally we want to put some reasonable constraints on our problem space, so we'll provide up to 5 results at a time. Supposing the user types "Smith", we could therefore fetch results using something like this (using Underscore):

var results = _.chain(people)
  .pluck('lastName')
  .filter(function(name) { return name.startsWith('Smith'); })
  .take(5)
  .value();

This query does a lot of stuff:

  • pluck('lastName'): iterates over the array and creates a new (potentially giant) array
  • filter(...): iterates over the new array, creating yet another (potentially giant) array
  • take(5): all that just for 5 elements!

So if performance and/or efficiency were a concern for you, you would probably not do things that way using Underscore. Instead, you'd likely go the procedural route:

var results = [];
for (var i = 0; i < people.length; ++i) {
  if (people[i].lastName.startsWith('Smith')) {
    results.push(people[i].lastName);
    if (results.length === 5) {
      break;
    }
  }
}

Thereโ€”now we haven't created any extraneous arrays, and we did all of the work in one iteration. Any problems?

Well, yeah. The main problem is that this is one-off code, which isn't reusable or particularly readable. If only we could somehow leverage the expressive power of Underscore but still get the performance of the hand-written procedural solution...


That's where Lazy.js comes in! Here's how we'd write the above query using Lazy.js:

var result = Lazy(people)
  .pluck('lastName')
  .filter(function(name) { return name.startsWith('Smith'); })
  .take(5);

Looks almost identical, right? That's the idea: Lazy.js aims to be completely familiar to JavaScript devs experienced with Underscore or Lodash. Every method from Underscore should have the same name and (almost) identical behavior in Lazy.js, except that instead of returning a fully-populated array on every call, it creates a sequence object with an each method.

What's important here is that no iteration takes place until you call each, and no intermediate arrays are created. Essentially Lazy.js combines all query operations into a "sequence" that behaves quite a bit like the procedural code we wrote a moment ago. (If you ever do want an array, simply call toArray on the resulting sequence.)

Of course, unlike the procedural approach, Lazy.js lets you keep your code clean and functional, and focus on solving whatever problem you're actually trying to solve instead of optimizing array traversals.

Features

So, Lazy.js is basically Underscore with lazy evaluation. Is that it?

Nope!

Indefinite sequence generation

The sequence-based paradigm of Lazy.js lets you do some pretty cool things that simply aren't possible with Underscore's array-based approach. One of these is the generation of indefinite sequences, which can go on forever, yet still support all of Lazy's built-in mapping and filtering capabilities.

Here's an example. Let's say we want 300 unique random numbers between 1 and 1000.

Lazy.generate(Math.random)
  .map(function(e) { return Math.floor(e * 1000) + 1; })
  .uniq()
  .take(300)
  .each(function(e) { console.log(e); });

Here's a slightly more advanced example: let's use Lazy.js to make a Fibonacci sequence.

var fibonacci = Lazy.generate(function() {
  var x = 1,
      y = 1;
  return function() {
    var prev = x;
    x = y;
    y += prev;
    return prev;
  };
}());

// Output: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
fibonacci.take(10).toArray();

OK, what else?

Asynchronous iteration

You've probably seen code snippets before that show how to iterate over an array asynchronously in JavaScript. But have you seen an example with functional goodness like this?

Lazy.generate(Lazy.identity)
  .async(1000) // specifies a 1-second interval between each element
  .map(function(x) { return String.fromCharCode(x + 65); })
  .take(26)
  .each(function(char) { console.log(char); });

All right... what else?

Event sequences

With indefinite sequences, we saw that unlike Underscore and Lodash, Lazy.js doesn't actually need an in-memory collection to iterate over. And asynchronous sequences demonstrate that it also doesn't need to do all its iteration at once.

Now here's a really cool combination of these two features: with a small extension to Lazy.js (lazy.browser.js, a separate file to include in browser-based environments), you can apply all of the power of Lazy.js to handling DOM events. In other words, Lazy.js lets you think of DOM events as a sequenceโ€”just like any otherโ€”and apply the usual map, filter, etc. functions on that sequence.

Here's an example. Let's say we want to handle all mousemove events on a given DOM element, and show their coordinates in one of two other DOM elements depending on location.

// First we define our "sequence" of events.
var mouseEvents = Lazy(sourceElement).on("mousemove");

// Map the Event objects to their coordinates, relative to the element.
var coordinates = mouseEvents.map(function(e) {
  var elementRect = sourceElement.getBoundingClientRect();
  return [
    Math.floor(e.clientX - elementRect.left),
    Math.floor(e.clientY - elementRect.top)
  ];
});

// For mouse events on one side of the element, display the coordinates in one place.
coordinates
  .filter(function(pos) { return pos[0] < sourceElement.clientWidth / 2; })
  .each(function(pos) { displayCoordinates(leftElement, pos); });

// For those on the other side, display them in a different place.
coordinates
  .filter(function(pos) { return pos[0] > sourceElement.clientWidth / 2; })
  .each(function(pos) { displayCoordinates(rightElement, pos); });

Anything else? Of course!

String processing

Now here's something you may not have even thought of: String.match and String.split. In JavaScript, each of these methods returns an array of substrings. If you think about it, this often means doing more work than necessary; but it's the quickest way (from a developer's standpoint) to get the job done.

For example, suppose you wanted the first five lines of a block of text. You could always do this:

var firstFiveLines = text.split("\n").slice(0, 5);

But of course, this actually splits the entire string into every single line. If the string is very large, this is quite wasteful.

With Lazy.js, we don't need to split up an entire string just to treat it as a sequence of lines. We can get the same effect by wrapping the string with Lazy and calling split:

var firstFiveLines = Lazy(text).split("\n").take(5);

This way we can read the first five lines of an arbitrarily large string (without pre-populating a huge array) and map/reduce on it just as with any other sequence.

Similarly with String.match: let's say we wanted to find the first 5 alphanumeric matches in a string. With Lazy.js, it's easy!

var firstFiveWords = Lazy(text).match(/[a-z0-9]+/i).take(5);

Piece of cake.

Stream processing

Lazy.js can wrap streams in Node.js as well.

Given any Readable Stream, you can wrap it with Lazy just as with arrays:

Lazy(stream)
  .take(5) // Read just the first 5 chunks of data read into the buffer.
  .each(processData);

For convenience, specialized helper methods for dealing with either file streams or HTTP streams are also offered. (Note: this API will probably change.)

// Read the first 5 lines from a file:
Lazy.readFile("path/to/file")
  .lines()
  .take(5)
  .each(doSomething);

// Read lines 5-10 from an HTTP response.
Lazy.makeHttpRequest("http://example.com")
  .lines()
  .drop(5)
  .take(5)
  .each(doSomething);

In each case, the elements in the sequence will be "chunks" of data most likely comprising multiple lines. The lines() method splits each chunk into lines (lazily, of course).


This library is experimental and still a work in progress.

More Repositories

1

nearest-color

Find the nearest color
JavaScript
340
star
2

autodoc

Doc generation on steroids
JavaScript
232
star
3

safe_yaml

Parse YAML safely
Ruby
215
star
4

ConcurrentList

A thread-safe, lock-free implementation of the IList<T> interface for .NET
C#
55
star
5

lemming.js

Evaluate user-input JS code
JavaScript
44
star
6

console-highlight

Syntax highlighting in the console
CSS
35
star
7

fast-matcher

Fast matching, e.g. for autocompletes
JavaScript
31
star
8

angular-fast-matcher

Angular directive based on fast-matcher
JavaScript
27
star
9

HighTables

A JavaScript library that makes it trivial to render charts from existing HTML tables
JavaScript
24
star
10

whatever.js

Just whatever
JavaScript
18
star
11

htmlout

HTML-styled console output
JavaScript
15
star
12

slidedown

Markdown slide decks
JavaScript
12
star
13

dm-noisy-failures

Noisy (and descriptive) failures for DataMapper
Ruby
12
star
14

simplex

Simpler than regular expressions
JavaScript
10
star
15

string-table

Format an array of data objects as a textual table
CoffeeScript
10
star
16

named-args

Named arguments in JavaScript
JavaScript
9
star
17

SublimeBucket

Bitbucket plugin for Sublime Text 3
Python
8
star
18

todo-backend-express

Node.js/Express implementation of the Todo-Backend API spec
JavaScript
8
star
19

apiif

API in folders
Ruby
7
star
20

6th-css-sense

I see dead CSS selectors
JavaScript
5
star
21

PhilosopherDeveloper

A repository for the blog The Philosopher Developer
CSS
5
star
22

gulp-esprima

Parse JS to ASTs w/ esprima
JavaScript
4
star
23

updown

Let your users vote
Ruby
4
star
24

CloudDevelop

An online development tool allowing coders to write and compile snippets of source code in a variety of languages, all within a web browser
JavaScript
4
star
25

deft

JS dependency declaration
JavaScript
4
star
26

NBinarySearch

Flexible binary search for all indexed collections in .NET
C#
4
star
27

race.js

Pit JavaScript libraries against each other
JavaScript
4
star
28

sortfix

Fixes Array.prototype.sort
JavaScript
2
star
29

SimpleDevelop

A cross-platform multi-language snippet compiler?
JavaScript
2
star
30

protips

I am a pro, and these are my tips
2
star
31

truman.js

Smoke & mirrors so you can prototype quickly
JavaScript
2
star
32

just-use-markdown

Put Markdown in <script> tags
JavaScript
2
star
33

ghdb

Ruby
1
star
34

randy

Generate random stuff
Ruby
1
star
35

shmomise

Promises, shmomises
JavaScript
1
star
36

VariantCollections

A library to provide some much-needed type variance to widely used collection classes from the .NET BCL
C#
1
star
37

fancy-server

Oh server, you so fancy
JavaScript
1
star
38

writer.js

Abstraction library for writing to console, strings
JavaScript
1
star
39

superout

When stdout just isn't enough
JavaScript
1
star
40

cleanSlate.js

Reset any pending asynchronous actions in the browser
JavaScript
1
star
41

validatorsquared

The validator validator
1
star
42

django-starter

Like sourdough starter, but replace sourdough with Django
Python
1
star
43

gquery

Generic jQuery
JavaScript
1
star
44

sketchy.js

Another HTML5 canvas drawing pad
JavaScript
1
star
45

Charter

Make your own web-embeddable charts really fast and easily
JavaScript
1
star
46

RPerft

Ruby gem for performance testing w/ Perft
Ruby
1
star
47

LiveDraft

Just an ideaโ€”we'll see where it goes
JavaScript
1
star