Skip to content
Eugene Lazutkin edited this page Nov 26, 2019 · 14 revisions

io.bundle provides high-level tools to orchestrate I/O. It bundles several requests in one transparently:

  • A bundle is transferred as one, receives a response, which is unbundled transparently too. Users should not modify their code.
  • Bundling can be done automatically using a time window, e.g., 40 milliseconds, or manually.
  • All current subscribers for a resource (who submitted io() requests for it) will be notified on unbundling.
    • Additionally results can be cached (see cache service), so future requests can be satisfied immediately.
    • It provides a foundation to request resources ahead of time.
  • User can request a resource and supply an additional bunch for related resources, which can be cached for future use.
  • A simple request can return a bundle, which will be stored in the cache for future use.
  • Pre-fetching: provisions are made to facilitate data requests before loading any JavaScript libraries to improve web pages' time-to-be-useful greatly.

In order to use bundling, a simple server-side code is required. A reference implementation in JavaScript is provided by heya-bundler. If you want to implement it in a different language, or an environment, take a look at protocol spec.

The rationale of bundling discussed in Why bundle?

Don't forget to consult the cookbook to see working snippets solving simple real-world problems.

This service requires track to be active. If used with cache, it can save bundled results for future use. In general, cache is a recommended companion service.

Logic

If options object has a Boolean property bundle, it tells the bundle service to process or to ignore a request. Otherwise, a default algorithm is used described in Service documentation.

Exposed properties

The following properties are available to customize all aspects of the advanced I/O handling.

io.bundle.start()

This function starts collecting a bundle: all eligible requests will be queued until io.bundle.commit() is called.

io.bundle.commit()

This function finishes collecting a bundle: all collected requests are assembled in bundles and sent to a bundler.

io.bundle.isStarted()

This function returns a Boolean value: true if we are collecting a bundle now, false otherwise.

io.bundle.waitTime

This property defines a number of milliseconds to wait before issuing any I/O requests and collect a bundle. Default: 20.

If it is 0, no time-based bundle collection takes place. Any other value causes the following behavior:

  • The first I/O request that is eligible for bundling (see io.bundle.canBeBundled()) issues io.bundle.start(), which places it in a queue.
  • A timer is started for io.bundle.waitTime milliseconds.
  • While the timer is active, all eligible I/O requests go to the queue.
  • When the timer is expired, io.bundle.commit() is issued, all collected requests are properly bundled, and issued.

Important: the bigger io.bundle.waitTime, the more I/O requests can be collected for bundling. But remember to avoid pitfalls:

  • The bigger the wait time, the bigger the delay before issuing a bundle. For example, if we wait for 10 seconds, it means that our first I/O request will be delayed by 10 whole seconds. It is very likely that instead of a speedup we got ourselves a massive delay.
  • Usually an application issues a limited amount of I/O requests before staring to wait for responses. It means that starting from a certain moment increasing the wait time will fail to collect more requests slowing down our application needlessly.

In many cases we want to collect requests made during a current thread. If this is a case, consider issuing io.bundle.start() and io.bundle.commit() with heya-defer facilities (see the Cookbook: bundle).

io.bundle.url

This property defines a URL where to send bundles. Default: '/bundle'.

io.bundle.minSize

This property defines a minimal bundle size as a number. Any bundle less than this number will be disassembled, and all requests will be issued individually as is. Default: 2.

The default value bundles 2 requests and more together but sends individual requests as is. If we want to send all requests as bundles, we can set it to 1.

io.bundle.maxSize

This property defines a maximal bundle size as a number. If we have more requests, they will be sent out in different bundles up to the maximal size. Default: 20.

Why do we need an upper limit on bundles? Server-Side implementation of a bundler implements an upper limit for security considerations: even if we can verify a source of bundles, having no limit, or very high limit, opens us to a DOS attack. While DOS is possible with regular requests, and we know how to handle those, a bundler can serve as a multiplier, and requires special care like putting an upper limit on a bundle.

Keep in mind that io.bundle.maxSize should correspond to an actual restriction on a server-side. While we can send bigger bundles, they can be rejected by a server, and we don't want that.

io.bundle.detect()

This function receives a data object as a single argument and returns an array of responses for unbundling, or null.

The default implementation does the following actions:

  • Checks if it is non-null object.
  • Checks if it complies to the bundle protocol:
    • Checks if there is a property named bundle, which value is 'bundle'.
    • Checks if there is a property named results, and it is an array.
  • If everything checks out, it returns results property.
  • Otherwise, null is returned.

io.bundle.unbundle()

This function is related to unbundling. It returns no value and takes one argument: a data object. That data object is checked with io.bundle.detect(), and if it recognized as a bundle response, it is unbundled, all waiting subscribers are notified, and results are cached for future use, if [cache}(cache) is used.

io.bundle.submit()

It is a helper. This procedure submits a bundle. It returns no value, and takes a single argument: an array of options.

If we already collecting a bundle (see io.bundle.isStarted() above), it adds to a bundle. Otherwise it opens a bundle with io.bundle.start(), submits all options, and commits a bundle immediately with io.bundle.commit().

This helper submits several I/O requests, yet return no promises. It is assumed that individual requests will be made before the bundle is in flight (see track), or their results will be cached (requires cache) for future use.

io.bundle.submitWithRelated()

It is a helper. This function submits a request with a related bundle. It takes two arguments:

  • options — the standard options object for a main request.
  • bundle — an array of options for a supplemental bundle.

It returns a promise for the main request. It is assumed that individual requests will be made before the bundle is in flight (see track), or their results will be cached (requires cache) for future use.

io.bundle.fly()

This procedure is related to pre-fetching. It registers a bundle as being in progress without initiating actual requests. It returns no value, and takes one argument: a bundle as an array of options.

See the Cookbook: bundle for examples of use.

io.bundle.pending

This is a dictionary object, which collects bundle requests. Keys are unique keys made by io.makeKey(options) or io.prepareRequest(options) (see the main API). Values are objects with following properties:

  • options — an original options object.
  • prep — an object produced by io.prepareRequest(options) (see the main API).
  • level — an index number of the bundle service.

Essentially all three properties are copies of the bundle service parameters. See Service for more details.

Standard service properties

See Service documentation for more details.