API reference

5.0.0-beta.1 · Published beta · b06750c5. Published on npm. Match your installed version.

Radio#

Use Radio to send events or request values between parts of an application that do not need a direct reference to each other. Channels keep those messages organized by name. Import Radio directly from Marionette:

import { Radio } from 'marionette';

Radio is included in marionette; it does not require a separate backbone.radio installation.

The built-in singleton does not share channels with backbone.radio. Migrate all application imports atomically, including code that publishes or requests outside Marionette classes; mixing both packages creates disconnected buses. See Atomic Radio migration.

Documentation Index#

Channels#

A channel provides a namespace for events and requests. Retrieve one with Radio.channel(name):

import { Radio } from 'marionette';

const notifications = Radio.channel('notifications');

Calling Radio.channel(name) again with the same name returns the same channel instance. A channel name is required. Channel names that match inherited object properties, such as toString, are treated as ordinary channel names.

Use new Channel(name) from @mnjs/radio for an independent message bus. It combines Events and Requests but does not join the registry. Its owner must call channel.reset() when finished. Radio.reset() covers registered channels. The named Channel export is Radio.Channel; an isolated runtime provides its own constructor at runtime.Radio.Channel.

For request/reply alone, import Requests from @mnjs/radio and compose it with Object.assign({}, Requests). It adds no event methods or registry.

Events#

Channels provide event-style messaging with methods including on, once, off, trigger, listenTo, and stopListening.

import { Radio } from 'marionette';

const session = Radio.channel('session');

session.on('expired', function(reason) {
  console.log(`Session expired: ${ reason }`);
});

session.trigger('expired', 'signed out remotely');
session.off('expired');

Use events when zero or more listeners may react to a notification and the sender does not need a return value.

Requests and Replies#

Channels also provide request/reply messaging. Register one reply with reply, then call it with request:

import { Radio } from 'marionette';

const account = Radio.channel('account');
const accountService = { currentUser: { id: 'example' } };

account.reply('current:user', function() {
  return this.currentUser;
}, accountService);

const currentUser = account.request('current:user');

Arguments passed after the request name are passed to the reply handler, and the handler's return value is returned from request. Invocation is synchronous: a thrown error reaches the caller immediately; a returned Promise is passed through unchanged and must be awaited or handled by the caller. Radio does not add cancellation, retries, or error handling.

A named handler takes precedence over a handler registered as default. The default handler receives (requestName, ...args). With neither handler, request returns undefined and may emit a debug warning. A non-function value registered with reply(name, value) is returned as-is for each request. Registering a second reply for the same name replaces the first; it does not multicast the request.

reply, replyOnce, and stopReplying return the channel or Requests receiver. A replyOnce handler is removed before invocation, including when it throws or makes a reentrant request. Removing it by its original callback before invocation also cancels it. Choose ordinary events when several independent listeners need to react to the same notification.

Only explicitly registered own handlers are eligible for a named request or the default fallback. Names matching inherited object properties, including constructor, toString, and __proto__, are ordinary request names. Result maps from object-form or space-separated requests likewise define safe own string properties. When an object-form key contains multiple space-separated names, the nested result contributes its own enumerable string and symbol properties; inherited and non-enumerable properties are ignored.

Use replyOnce for a handler that should be removed after its first request. Use stopReplying to remove one or more handlers:

account.replyOnce('status:ready', () => true);
account.stopReplying('current:user');

The request registration methods retain Backbone.Radio's customization seams: replyOnce installs its wrapper through overridable reply, and map or space-separated reply, replyOnce, and stopReplying calls dispatch each entry through the corresponding public method. For object-form request, the mapped value is the first handler argument and any arguments after the map are forwarded after it.

Use requests when one handler owns an operation or when the sender needs a return value.

Debugging#

Enable Radio debug warnings with setDebug:

import { Radio } from 'marionette';

Radio.setDebug();

Debug mode warns when a request handler is overwritten or an unhandled request is made. Disable it explicitly when it is no longer needed:

Radio.setDebug(false);

Radio.log(channelName, eventName, ...args) receives activity from tuneIn(). Radio.debugLog(warning, eventName, channelName) receives warnings while debug mode is enabled. Assign either hook to route output to an application logger or test collector. Both default to console output.

import { createMarionette } from 'marionette';

const runtime = createMarionette();
runtime.Radio.debugLog = (warning, eventName, channelName) => {
  console.warn({ warning, eventName, channelName });
};
runtime.Radio.setDebug();

Hooks belong to each Radio instance and run with that Radio as this. Replacing a hook affects existing channels, including tuned channels. Disabling debug mode also disables delivery to custom warning hooks. Exceptions from hooks propagate. Standalone Channel and Requests imports use the default Radio's warning configuration; new runtime.Radio.Channel(name) uses that runtime's configuration.

Channel Lifecycle#

Channels are shared by name within their Radio runtime and remain available for that runtime's lifetime. Root imports use one default Radio. Each createMarionette() call returns an isolated Radio and channel registry. Clean up handlers when their owning object or feature is destroyed:

import { MnObject, Radio } from 'marionette';

const owner = new MnObject();
const channel = Radio.channel('feature');

owner.stopListening(channel);
channel.off(null, null, owner);
channel.stopReplying(null, null, owner);

The matching cleanup depends on whether the owner used listenTo, on, or reply to register the handler.

Call channel.reset() to remove all event listeners, listening relationships, and reply handlers from that channel. Radio.reset(name) resets one existing channel, while Radio.reset() resets all existing channels.

Resetting a channel clears its handlers but does not replace the shared channel instance. Prefer targeted cleanup for long-lived application channels so one feature does not remove another feature's handlers.

Operation Unknown channel Existing channel
Radio.channel(name) Creates and registers the channel. Returns the same channel.
Top-level event, request, and tuning methods Create the channel through Radio.channel(name). Operate on the same channel.
Radio.reset(name) Throws MarionetteError with code MN0021 without creating a channel. Clears handlers and preserves the channel identity.
Radio.reset() Does not create channels. Resets every registered channel without replacing it.

Only a zero-argument Radio.reset() call means reset all. Supplying an empty or otherwise falsy channel name throws the existing required-name diagnostic MN0017 without resetting any channel.

Marionette Integration#

Application and MnObject can bind events and requests to a channel with channelName, radioEvents, and radioRequests. getChannel() returns the configured channel.

radioEvents follows the entity-event map contract, including the MN0026 rejection of an own enumerable __proto__ map entry. The direct Radio Events API continues to support __proto__ as an event name.

import { MnObject, Radio } from 'marionette';

export const Notifications = MnObject.extend({
  channelName: 'notifications',

  initialize() {
    this.messages = [];
  },

  radioEvents: {
    'message:received': 'showMessage'
  },

  radioRequests: {
    'message:count': 'getMessageCount'
  },

  showMessage(message) {
    this.messages.push(message);
  },

  getMessageCount() {
    return this.messages.length;
  }
});

export const notifications = new Notifications();
export const channel = Radio.channel('notifications');
const message = { text: 'Hello' };

channel.trigger('message:received', message);
const count = channel.request('message:count');

Destroying the Marionette object removes request handlers bound with that object as their context. The object's event listeners are cleaned up through the normal Marionette event lifecycle.

Backbone.Radio comparison#

The v5 Radio implementation retains Backbone.Radio's channel messaging model, but it is not a drop-in replacement for every exported property. @mnjs/radio can be used independently; core re-exports the same default Radio within each module format.

Area v5 behavior
Requests and replies Named/default handlers, callback context, map and space-separated forms, one-time replies, and selective removal retain the messaging contract.
Events and cleanup Channels use shared Marionette Events. Reset clears handlers and owned listeners while retaining channel identity. Events also provides triggerMethod.
Debugging Use setDebug() instead of assigning DEBUG. log and debugLog are replaceable per-instance hooks, and the debug toggle gates custom warning hooks too. Removing an absent reply does not warn.
Construction and globals Use channel(name) for registered channels, new Channel(name) for standalone channels, or the named Requests mixin for request/reply alone. VERSION, Backbone global installation, and noConflict() are not Radio exports.
Names and maps Request maps use own enumerable string keys. Inherited entries are ignored; names such as __proto__ are supported without changing object prototypes.
Reset arguments Only reset() resets all channels. An explicitly supplied empty name is an error; an unknown named channel gets a Marionette diagnostic.

test/unit/radio-parity.spec.js runs shared behavioral scenarios against the published Backbone.Radio 2.0.0 runtime and Marionette: fallback arguments, flat values, nested request maps, reentrant and throwing one-time handlers, callback and context removal, top-level forwarding, and channel/listener cleanup.

The extraction was also checked against the upstream request, channel, forwarding, tuning, and debug tests at Backbone.Radio commit 7a58ade. That comparison adapts private registry names and the old debug toggle; tests requiring removed public APIs are differences, not claims of full compatibility.

One intentional correction relative to the published 2.0.0 bundle is cancellation of replyOnce by its original callback: stopReplying(name, callback) removes the pending reply in v5. The published bundle and inspected upstream source leave it registered. This follows the callback-identity behavior of Backbone.Events once/off. The comparison test records the difference explicitly instead of claiming exact parity.