🏠 back to Observable

Loading js puzzle

Basic question. Why does this “work” in the sense that the Bokeh library becomes available for later use in the notebook:

Bokeh = {
  const ver = '2.0.2';
  if (!document.getElementById('bokehjs-css'))
    document.head.appendChild(html`<link id='bokehjs-css' href='https://cdn.pydata.org/bokeh/release/bokeh-${ver}.min.css' rel=stylesheet>`);
  await require(`https://cdn.pydata.org/bokeh/release/bokeh-${ver}.min.js`).catch(() => {})
  const Bokeh = window.Bokeh;
  await require(`https://cdn.pydata.org/bokeh/release/bokeh-api-${ver}.min.js`).catch(() => {});
  return Bokeh; 
}

But taking each statement in that block and making it a separate cell doesn’t work, as in
this notebook.

Thanks to this notebook for the code above.

Cells run in order of dependence on one another and, beyond that, it’s not deterministic. Now, consider these three lines:

await require(`https://cdn.pydata.org/bokeh/release/bokeh-${ver}.min.js`).catch(() => {})
const Bokeh = window.Bokeh;
await require(`https://cdn.pydata.org/bokeh/release/bokeh-api-${ver}.min.js`).catch(() => {});

The first line imports the library and adds the Bokeh object to the window. The second line defines Bokeh to be that object. The third line then modifies the Bokeh object. If you do those as separate cells, it’s hard to say exactly what any reference to Bokeh will refer to.

Here’s a simpler example where it’s a bit easier to see what can happen:

obj = ({})
obj.a = 1
obj

Here’s a screenshot of what that looks like when you enter those as separate cells:

Screen Shot 2020-05-12 at 6.22.53 PM

Note that the named version of obj is not updated with the value of the a property. Any cell that refers to obj.a will have a problem.

1 Like

Observable behaves more like a spreadsheet than a sequential programming environment:

The idea is that you can redefine a cell (by editing the code, by changing a value it references, or by interacting with the page), and then Observable will automatically recompute the cell’s value and every other cell that references that value.

This means you want to avoid side-effects in your cells—things like mutating the DOM and mutating values defined by other cells. From the Learn D3 tutorial:

A subtle consideration when working with data in Observable is whether to put code in a single cell or separate cells. A good rule of thumb is that a cell should either define a named value to be referenced by other cells (such as data above), or it should display something informative to the reader (such as this prose, the chart above, or cells which inspect the data).

A critical implication of this rule is that you should avoid implicit dependencies between cells: only depend on what you can name.

Why? A notebook’s dataflow is built from its cell references. If a cell doesn’t have a name, it cannot be referenced and cells that implicitly depend on its effect may run before it. (Cells run in topological order, not top-to-bottom.) Implicit dependencies lead to nondeterministic behavior :scream: during development, and possible errors :boom: when the notebook is reloaded!

For example, if one cell defines an array and a second cell modifies it in-place, other cells may see the array before or after the mutation. To avoid this nondeterminism, make the dependency explicit by giving the second cell a name and copying the array using Array.from or array.map. Or combine the two cells so that only the already-modified array is visible to other cells.

1 Like

Clearly this will take some getting used to! Thanks for the clear explanation.

1 Like