Home Reference Source

packages/oo7/src/bond.js

  1. // (C) Copyright 2016-2017 Parity Technologies (UK) Ltd.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14.  
  15. const BondCache = require('./bondCache');
  16.  
  17. var subscripted = {};
  18. // Any names which should never be subscripted.
  19. const reservedNames = { toJSON: true, toString: true };
  20.  
  21. function symbolValues (o) {
  22. return Object.getOwnPropertySymbols(o).map(k => o[k]);
  23. }
  24.  
  25. function equivalent (a, b) {
  26. return JSON.stringify(a) === JSON.stringify(b);
  27. }
  28.  
  29. /**
  30. * An object which tracks a single, potentially variable, value.
  31. * {@link Bond}s may be updated to new values with {@link Bond#changed} and reset to an indeterminate
  32. * ("not ready") value with {@link Bond#reset}.
  33. *
  34. * {@link Bond}s track their dependents - aspects of the program, including other {@link Bond}s,
  35. * which reference their current value. Dependents may be added with {@link Bond#use} and
  36. * removed with {@link Bond#drop}.
  37. *
  38. * A {@link Bond} may be tied to a particular function to ensure it is called whenever
  39. * the value changes. This implies a dependency, and can be registered with {@link Bond#tie} and
  40. * dropped with {@link Bond#untie}. A function may also be called should the {@link Bond} be reverted
  41. * to an undefined value; in this case {@link Bond#notify} and {@link Bond#unnotify} should
  42. * be used.
  43. *
  44. * {@link Bond}s can be made to execute a function once their value becomes ready
  45. * using {@link Bond#then}, which in some sense replicates the same function in the
  46. * context of a `Promise`. The similar function {@link Bond#done} is also supplied which
  47. * executes a given function when the {@link Bond} reaches a value which is considered
  48. * "final", determined by {@link Bond#isDone} being implemented and `true`. Precisely
  49. * what any given {@link Bond} considers final depends entirely on the subclass of
  50. * {@link Bond}; for the {@link Bond} class itself, `isDone` always returns `false` and thus
  51. * {@link Bond#done} is unusable. The value of the {@link Bond}, once _ready_, may
  52. * be logged to the console with the {@link Bond#log} function.
  53. *
  54. * A {@link Bond} can provide a derivative {@link Bond} whose value reflects the "readiness"
  55. * of the original, using {@link Bond#ready} and conversely {@link Bond#notReady}. This
  56. * can also be queried normally with {@link Bond#isReady}.
  57. *
  58. * One or a number of {@link Bond}s can be converted into a single {Promise} with the
  59. * {@link Bond#promise} function.
  60. *
  61. * `Bonds` can be composed. {@link Bond#map} creates a new {@link Bond} whose value is a
  62. * transformation. {@link Bond.all} creates a new {@link Bond} which evaluates to the array
  63. * of values of each of a number of dependent {@link Bond}s. {@link Bond.mapAll} combines
  64. * both. {@link Bond#reduce} allows a {@link Bond} that evaluates to array to be
  65. * transformed into some other value recursively.
  66. *
  67. * {@link Bond#sub} forms a derivative {@link Bond} as the subscript (square-bracket
  68. * indexing). {@link Bond#subscriptable} may be used to return a `Proxy` object that
  69. * allows the {@link Bond} to be subscripted (square-bracket indexed) directly without
  70. * need of the {@link Bond#sub} function.
  71. *
  72. * {@link Bond} is built to be subclassed. When subclassing, three functions are
  73. * useful to implement. {@link Bond#isDone} may be implemented
  74. * in order to make {@link Bond#done} be useful. {@link Bond#initialise} is called exactly once
  75. * when there becomes at least one dependent; {@link Bond#finalise} is called when there
  76. * are no longer any dependents.
  77. *
  78. * _WARNING_: You should not attempt to use the `toString` function with this
  79. * class. It cannot be meaningfully converted into a string, and to attempt it
  80. * will give an undefined result.
  81. */
  82. class Bond {
  83. /**
  84. * Constructs a new {@link Bond} object whose value is _not ready_.
  85. *
  86. * @param {boolean} mayBeNull - `true` if this instance's value may ever
  87. * validly be `null`. If `false`, then setting this object's value to `null`
  88. * is equivalent to reseting back to being _not ready_.
  89. */
  90. constructor (mayBeNull = true, cache = null) {
  91. // Functions that should execute whenever we resolve to a new, "ready"
  92. // value. They are passed the new value as a single parameter.
  93. // Each function is mapped to from a `Symbol`, which can be used to
  94. // remove it.
  95. this._subscribers = {};
  96. // Equivalent to `_subscribers`, except that after executing, the
  97. // function is removed from this array. No mapping is provided so they
  98. // cannot be removed except by triggering.
  99. this._thens = [];
  100. // Functions that should execute whenever either the resolved value
  101. // changes, or our readiness changes. No parameters are passed.
  102. // Each function is mapped to from a `Symbol`, which can be used to
  103. // remove it.
  104. this._notifies = {};
  105.  
  106. // Are we resolved to a value at all. If `false`, we are not yet
  107. // resolved to a value and `_value` is meaningless.
  108. this._ready = false;
  109. // Our currently resolved value, if any.
  110. this._value = null;
  111. // Is the value in the middle of having an update triggered?
  112. this._triggering = null;
  113.  
  114. // Is it valid to resolve to `null`? By default it is value.
  115. this._mayBeNull = mayBeNull;
  116.  
  117. // The reference count of the number of dependents. If zero, then there
  118. // is no need to go to any effort to track changes. This is used for
  119. // specialisations where tracking changes requires holding or managing
  120. // resources.
  121. // This is never smaller but can be larger than the total number of
  122. // callbacks registered between `_subscribers`, `_thens` and
  123. // `_notifies`.
  124. this._users = 0;
  125.  
  126. // The Universally Unique ID, a string used to manage caching and
  127. // inter-tab result sharing.
  128. this._uuid = cache ? cache.id : null;
  129. // A method for stringifying this Bond's result when using with the cache.
  130. this._stringify = cache ? cache.stringify : null;
  131. // A method for unstringifying this Bond's result when using with the cache.
  132. this._parse = cache ? cache.parse : null;
  133. }
  134.  
  135. toString () {
  136. // Bonds make little sense as strings, and our subscripting trick (where
  137. // we are able to use Bonds as keys) only works if we can coerce into a
  138. // string. We store the reverse lookup (symbol -> Bond) in a global
  139. // table `subscripted` so that it can be retrieved while interpreting
  140. // the subscript in the code Proxy code found in `subscriptable`.
  141. let s = Symbol('Bond');
  142. subscripted[s] = this;
  143. return s;
  144. }
  145.  
  146. /**
  147. * Provides a transparently subscriptable version of this object.
  148. *
  149. * The object that is returned from this function is a convenience `Proxy`
  150. * which acts exactly equivalent
  151. * to the original {@link Bond}, except that any subscripting of fields that are
  152. * not members of the {@link Bond} object will create a new {@link Bond} that
  153. * itself evaluates to this {@link Bond}'s value when subscripted with the same
  154. * field.
  155. *
  156. * @example
  157. * let x = (new Bond).subscriptable();
  158. * let y = x.foo;
  159. * y.log(); // nothing yet
  160. * x.changed({foo: 42, bar: 69}); // logs 42
  161. *
  162. * @param {number} depth - The maximum number of levels of subscripting that
  163. * the returned `Proxy` will support.
  164. * @returns {Proxy} - `Proxy` object that acts as a subscriptable variation
  165. * for convenience.
  166. */
  167. subscriptable (depth = 1) {
  168. // No subscripting at all if depth is 0.
  169. // We will recurse if > 1.
  170. if (depth === 0) { return this; }
  171.  
  172. let r = new Proxy(this, {
  173. // We proxy the get object field:
  174. get (receiver, name) {
  175. // Skip the magic proxy and just interpret directly if the field
  176. // name is a string/number and it's either an extent key in the
  177. // underlying `Bond` or it's a reserved field name (e.g. toString).
  178. if (
  179. (typeof (name) === 'string' || typeof (name) === 'number') &&
  180. (reservedNames[name] || typeof (receiver[name]) !== 'undefined')
  181. ) {
  182. return receiver[name];
  183. }
  184.  
  185. // If it's a symbolic key, then it's probably a `Bond` symbolified
  186. // in our toString function. Look it up in the global Bond symbol
  187. // table and recurse into one less depth.
  188. if (typeof (name) === 'symbol') {
  189. if (Bond._knowSymbol(name)) {
  190. return receiver
  191. .sub(Bond._fromSymbol(name))
  192. .subscriptable(depth - 1);
  193. } else {
  194. // console.warn(`Unknown symbol given`);
  195. return null;
  196. }
  197. }
  198. // console.log(`Subscripting: ${JSON.stringify(name)}`)
  199. // Otherwise fall back with a simple subscript and recurse
  200. // back with one less depth.
  201. return receiver.sub(name).subscriptable(depth - 1);
  202. }
  203. });
  204. return r;
  205. }
  206.  
  207. // Check to see if there's a symbolic reference for a Bond.
  208. static _knowSymbol (name) {
  209. return !!subscripted[name];
  210. }
  211. // Lookup a symbolic Bond reference and remove it from the global table.
  212. static _fromSymbol (name) {
  213. let sub = subscripted[name];
  214. delete subscripted[name];
  215. return sub;
  216. }
  217.  
  218. /**
  219. * Alters this object so that it is always _ready_.
  220. *
  221. * If this object is ever {@link Bond#reset}, then it will be changed to the
  222. * value given.
  223. *
  224. * @example
  225. * let x = (new Bond).defaultTo(42);
  226. * x.log(); // 42
  227. * x.changed(69);
  228. * x.log(); // 69
  229. * x.reset();
  230. * x.log() // 42
  231. *
  232. * @param {*} x - The value that this object represents if it would otherwise
  233. * be _not ready_.
  234. * @returns {@link Bond} - This (mutated) object.
  235. */
  236. defaultTo (_defaultValue) {
  237. this._defaultTo = _defaultValue;
  238. if (!this._ready) {
  239. this.trigger(_defaultValue);
  240. }
  241. return this;
  242. }
  243.  
  244. /**
  245. * Resets the state of this Bond into being _not ready_.
  246. *
  247. * Any functions that are registered for _notification_ (see {@link Bond#notify})
  248. * will be called if this {@link Bond} is currently _ready_.
  249. */
  250. reset () {
  251. if (this._defaultTo !== undefined) {
  252. this.trigger(this._defaultTo);
  253. return;
  254. }
  255. if (this._ready) {
  256. this._ready = false;
  257. this._value = null;
  258. symbolValues(this._notifies).forEach(callback => callback());
  259. }
  260. }
  261. /**
  262. * Makes the object _ready_ and sets its current value.
  263. *
  264. * Any functions that are registered for _notification_ (see {@link Bond#notify})
  265. * or are _tied_ (see {@link Bond#tie}) will be called if this {@link Bond} is not
  266. * currently _ready_ or is _ready_ but has a different value.
  267. *
  268. * This function is a no-op if the JSON representations of `v` and of the
  269. * current value, if any, are equal.
  270. *
  271. * @param {*} v - The new value that this object should represent. If `undefined`
  272. * then the function does nothing.
  273. */
  274. changed (newValue) {
  275. if (typeof (newValue) === 'undefined') {
  276. return;
  277. }
  278. // console.log(`maybe changed (${this._value} -> ${v})`);
  279. if (!this._mayBeNull && newValue === null) {
  280. this.reset();
  281. } else if (!this._ready || !equivalent(newValue, this._value)) {
  282. this.trigger(newValue);
  283. }
  284. }
  285.  
  286. /**
  287. * Makes the object _ready_ and sets its current value.
  288. *
  289. * Any functions that are registered for _notification_ (see {@link Bond#notify})
  290. * or are _tied_ (see {@link Bond#tie}) will be called if this {@link Bond} is not
  291. * currently _ready_ or is _ready_ but has a different value.
  292. *
  293. * Unlike {@link Bond#changed}, this function doesn't check equivalence
  294. * between the new value and the current value.
  295. *
  296. * @param {*} v - The new value that this object should represent. By default,
  297. * it will reissue the current value. It is an error to call it without
  298. * an argument if it is not _ready_.
  299. */
  300. trigger (newValue = this._value) {
  301. // Cannot trigger to an undefined value (just reset it or call with `null`).
  302. if (typeof (newValue) === 'undefined') {
  303. console.error(`Trigger called with undefined value`);
  304. return;
  305. }
  306. // Cannot trigger as a recourse to an existing trigger.
  307. if (this._triggering !== null) {
  308. console.error(`Trigger cannot be called while already triggering.`, this._triggering.becoming, newValue);
  309. return;
  310. }
  311. this._triggering = { becoming: newValue };
  312.  
  313. if (!this._mayBeNull && newValue === null) {
  314. this.reset();
  315. } else {
  316. // console.log(`firing (${JSON.stringify(v)})`);
  317. this._ready = true;
  318. this._value = newValue;
  319. symbolValues(this._notifies).forEach(callback => callback());
  320. symbolValues(this._subscribers).forEach(callback => callback(this._value));
  321. this._thens.forEach(callback => {
  322. callback(this._value);
  323. this.drop();
  324. });
  325. this._thens = [];
  326. }
  327.  
  328. this._triggering = null;
  329.  
  330. if (this._uuid && !this._noCache && Bond.cache) {
  331. Bond.cache.changed(this._uuid, newValue);
  332. }
  333. }
  334.  
  335. /**
  336. * Register a single dependency for this object.
  337. *
  338. * Notes that the object's value is in use, and that it should be computed.
  339. * {@link Bond} sub-classes are allowed to not work properly unless there is
  340. * at least one dependency registered.
  341. *
  342. * @see {@link Bond#initialise}, {@link Bond#finalise}.
  343. */
  344. use () {
  345. if (this._users === 0) {
  346. if (!this._uuid || !!this._noCache || !Bond.cache) {
  347. this.initialise();
  348. } else {
  349. Bond.cache.initialise(this._uuid, this, this._stringify, this._parse);
  350. }
  351. }
  352. this._users++;
  353. return this;
  354. }
  355.  
  356. /**
  357. * Unregister a single dependency for this object.
  358. *
  359. * Notes that a previously registered dependency has since expired. Must be
  360. * called exactly once for each time {@link Bond#use} was called.
  361. */
  362. drop () {
  363. if (this._users === 0) {
  364. throw new Error(`mismatched use()/drop(): drop() called once more than expected!`);
  365. }
  366. this._users--;
  367. if (this._users === 0) {
  368. if (!this._uuid || !!this._noCache || !Bond.cache) {
  369. this.finalise();
  370. } else {
  371. Bond.cache.finalise(this._uuid, this);
  372. }
  373. }
  374. }
  375.  
  376. /**
  377. * Initialise the object.
  378. *
  379. * Will be called at most once before an accompanying {@link Bond#finalise}
  380. * and should initialise/open/create any resources that are required for the
  381. * sub-class to maintain its value.
  382. *
  383. * @access protected
  384. */
  385. initialise () {}
  386.  
  387. /**
  388. * Uninitialise the object.
  389. *
  390. * Will be called at most once after an accompanying {@link Bond#initialise}
  391. * and should close/finalise/drop any resources that are required for the
  392. * sub-class to maintain its value.
  393. *
  394. * @access protected
  395. */
  396. finalise () {}
  397.  
  398. /**
  399. * Returns whether the object is currently in a terminal state.
  400. *
  401. * _WARNING_: The output of this function should not change outside of a
  402. * value change. If it ever changes without the value changing, `trigger`
  403. * should be called to force an update.
  404. *
  405. * @returns {boolean} - `true` when the value should be interpreted as being
  406. * in a final state.
  407. *
  408. * @access protected
  409. * @see {@link Bond#done}
  410. */
  411. isDone () { return false; }
  412.  
  413. /**
  414. * Notification callback.
  415. * @callback Bond~notifyCallback
  416. */
  417.  
  418. /**
  419. * Register a function to be called when the value or the _readiness_
  420. * changes.
  421. *
  422. * Calling this function already implies calling {@link Bond#use} - there
  423. * is no need to call both.
  424. *
  425. * Use this only when you need to be notified should the object be reset to
  426. * a not _ready_ state. In general you will want to use {@link Bond#tie}
  427. * instead.
  428. *
  429. * @param {Bond~notifyCallback} f - The function to be called. Takes no parameters.
  430. * @returns {Symbol} An identifier for this registration. Must be provided
  431. * to {@link Bond#unnotify} when the function no longer needs to be called.
  432. */
  433. notify (callback) {
  434. this.use();
  435. let id = Symbol('notify::id');
  436. this._notifies[id] = callback;
  437. if (this._ready) {
  438. callback();
  439. }
  440. return id;
  441. }
  442.  
  443. /**
  444. * Unregister a function previously registered with {@link Bond#notify}.
  445. *
  446. * Calling this function already implies calling {@link Bond#drop} - there
  447. * is no need to call both.
  448. *
  449. * @param {Symbol} id - The identifier returned from the corresponding
  450. * {@link Bond#notify} call.
  451. */
  452. unnotify (id) {
  453. delete this._notifies[id];
  454. this.drop();
  455. }
  456.  
  457. /**
  458. * Tie callback.
  459. * @callback Bond~tieCallback
  460. * @param {&} value - The current value to which the object just changed.
  461. * @param {Symbol} id - The identifier of the registration for this callback.
  462. */
  463.  
  464. /**
  465. * Register a function to be called when the value changes.
  466. *
  467. * Calling this function already implies calling {@link Bond#use} - there
  468. * is no need to call both.
  469. *
  470. * Unlike {@link Bond#notify}, this does not get
  471. * called should the object become reset into being not _ready_.
  472. *
  473. * @param {Bond~tieCallback} f - The function to be called.
  474. * @returns {Symbol} - An identifier for this registration. Must be provided
  475. * to {@link Bond#untie} when the function no longer needs to be called.
  476. */
  477. tie (callback) {
  478. this.use();
  479. let id = Symbol('tie::id');
  480. this._subscribers[id] = callback;
  481. if (this._ready) {
  482. callback(this._value, id);
  483. }
  484. return id;
  485. }
  486.  
  487. /**
  488. * Unregister a function previously registered with {@link Bond#tie}.
  489. *
  490. * Calling this function already implies calling {@link Bond#drop} - there
  491. * is no need to call both.
  492. *
  493. * @param {Symbol} id - The identifier returned from the corresponding
  494. * {@link Bond#tie} call.
  495. */
  496. untie (id) {
  497. delete this._subscribers[id];
  498. this.drop();
  499. }
  500.  
  501. /**
  502. * Determine if there is a definite value that this object represents at
  503. * present.
  504. *
  505. * @returns {boolean} - `true` if there is presently a value that this object represents.
  506. */
  507. isReady () { return this._ready; }
  508.  
  509. /**
  510. * Provide a derivative {@link Bond} which represents the same as this object
  511. * except that before it is ready it evaluates to a given default value and
  512. * after it becomes ready for the first time it stays fixed to that value
  513. * indefinitely.
  514. *
  515. * @param {Symbol} defaultValue - The value that the new bond should take when
  516. * this bond is not ready.
  517. * @returns {@link Bond} - Object representing the value returned by
  518. * this {@link Bond} except that it evaluates to the given default value when
  519. * this bond is not ready and sticks to the first value that made it ready.
  520. */
  521. latched (defaultValue = undefined, mayBeNull = undefined, cache = null) {
  522. const LatchBond = require('./latchBond');
  523.  
  524. return new LatchBond(
  525. this,
  526. typeof defaultValue === 'undefined' ? undefined : defaultValue,
  527. typeof mayBeNull === 'undefined' ? undefined : mayBeNull,
  528. cache
  529. );
  530. }
  531.  
  532. /**
  533. * Provide a {@link Bond} which represents the same as this object except that
  534. * it takes a particular value when this would be unready.
  535. *
  536. * @param {Symbol} defaultValue - The value that the new bond should take when
  537. * this bond is not ready.
  538. * @returns {@link Bond} - Object representing the value returned by
  539. * this {@link Bond} except that it evaluates to the given default value when
  540. * this bond is not ready. The returned object itself is always _ready_.
  541. */
  542. default (defaultValue = null) {
  543. const DefaultBond = require('./defaultBond');
  544.  
  545. return new DefaultBond(defaultValue, this);
  546. }
  547.  
  548. /**
  549. * Provide a {@link Bond} which represents whether this object itself represents
  550. * a particular value.
  551. *
  552. * @returns {@link Bond} - Object representing the value returned by
  553. * this {@link Bond}'s {@link Bond#isReady} result. The returned object is
  554. * itself always _ready_.
  555. */
  556. ready () {
  557. const ReadyBond = require('./readyBond');
  558.  
  559. if (!this._readyBond) {
  560. this._readyBond = new ReadyBond(this);
  561. }
  562. return this._readyBond;
  563. }
  564.  
  565. /**
  566. * Convenience function for the logical negation of {@link Bond#ready}.
  567. *
  568. * @example
  569. * // These two expressions are exactly equivalent:
  570. * bond.notReady();
  571. * bond.ready().map(_ => !_);
  572. *
  573. * @returns {@link Bond} Object representing the logical opposite
  574. * of the value returned by
  575. * this {@link Bond}'s {@link Bond#isReady} result. The returned object is
  576. * itself always _ready_.
  577. */
  578. notReady () {
  579. const NotReadyBond = require('./notReadyBond');
  580.  
  581. if (!this._notReadyBond) {
  582. this._notReadyBond = new NotReadyBond(this);
  583. }
  584. return this._notReadyBond;
  585. }
  586.  
  587. /**
  588. * Then callback.
  589. * @callback Bond~thenCallback
  590. * @param {*} value - The current value to which the object just changed.
  591. */
  592.  
  593. /**
  594. * Register a function to be called when this object becomes _ready_.
  595. *
  596. * For an object to be considered _ready_, it must represent a definite
  597. * value. In this case, {@link Bond#isReady} will return `true`.
  598. *
  599. * If the object is already _ready_, then `f` will be called immediately. If
  600. * not, `f` will be deferred until the object assumes a value. `f` will be
  601. * called at most once.
  602. *
  603. * @param {Bond~thenCallback} f The callback to be made once the object is ready.
  604. *
  605. * @example
  606. * let x = new Bond;
  607. * x.then(console.log);
  608. * x.changed(42); // 42 is written to the console.
  609. */
  610. then (callback) {
  611. this.use();
  612. if (this._ready) {
  613. callback(this._value);
  614. this.drop();
  615. } else {
  616. this._thens.push(callback);
  617. }
  618. return this;
  619. }
  620.  
  621. /**
  622. * Register a function to be called when this object becomes _done_.
  623. *
  624. * For an object to be considered `done`, it must be _ready_ and the
  625. * function {@link Bond#isDone} should exist and return `true`.
  626. *
  627. * If the object is already _done_, then `f` will be called immediately. If
  628. * not, `f` will be deferred until the object assumes a value. `f` will be
  629. * called at most once.
  630. *
  631. * @param {Bond~thenCallback} f The callback to be made once the object is ready.
  632. *
  633. * @example
  634. * let x = new Bond;
  635. * x.then(console.log);
  636. * x.changed(42); // 42 is written to the console.
  637. */
  638. done (callback) {
  639. if (this.isDone === undefined) {
  640. throw new Error('Cannot call done() on Bond that has no implementation of isDone.');
  641. }
  642. var id;
  643. let cleanupCallback = newValue => {
  644. if (this.isDone(newValue)) {
  645. callback(newValue);
  646. this.untie(id);
  647. }
  648. };
  649. id = this.tie(cleanupCallback);
  650. return this;
  651. }
  652.  
  653. /**
  654. * Logs the current value to the console.
  655. *
  656. * @returns {@link Bond} The current object.
  657. */
  658. log () { this.then(console.log); return this; }
  659.  
  660. /**
  661. * Maps the represented value to a string.
  662. *
  663. * @returns {@link Bond} A new {link Bond} which represents the `toString`
  664. * function on whatever value this {@link Bond} represents.
  665. */
  666. mapToString () {
  667. return this.map(_ => _.toString());
  668. }
  669.  
  670. /**
  671. * Make a new {@link Bond} which is the functional transformation of this object.
  672. *
  673. * @example
  674. * let b = new Bond;
  675. * let t = b.map(_ => _ * 2);
  676. * t.tie(console.log);
  677. * b.changed(21); // logs 42
  678. * b.changed(34.5); // logs 69
  679. *
  680. * @example
  681. * let b = new Bond;
  682. * let t = b.map(_ => { let r = new Bond; r.changed(_ * 2); return r; });
  683. * t.tie(console.log);
  684. * b.changed(21); // logs 42
  685. * b.changed(34.5); // logs 69
  686. *
  687. * @example
  688. * let b = new Bond;
  689. * let t = b.map(_ => { let r = new Bond; r.changed(_ * 2); return [r]; }, 1);
  690. * t.tie(console.log);
  691. * b.changed(21); // logs [42]
  692. * b.changed(34.5); // logs [69]
  693. *
  694. * @param {function} transform - The transformation to apply to the value represented
  695. * by this {@link Bond}.
  696. * @param {number} outResolveDepth - The number of levels deep in any array
  697. * object values of the result of the transformation that {@link Bond} values
  698. * will be resolved.
  699. * @default 3
  700. * @param {*} cache - Cache information. See constructor.
  701. * @default null
  702. * @param {*} latched - Should the value be latched so that once ready it stays ready?
  703. * @default false
  704. * @param {*} mayBeNull - Should the value be allowed to be `null` such that if it ever becomes
  705. * null, it is treated as being unready?
  706. * @default true
  707. * @returns {@link Bond} - An object representing this object's value with
  708. * the function `transform` applied to it.
  709. */
  710. map (transform, outResolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
  711. const TransformBond = require('./transformBond');
  712. return new TransformBond(transform, [this], [], outResolveDepth, 3, cache, latched, mayBeNull);
  713. }
  714.  
  715. /**
  716. * Just like `map`, except that it defaults to no latching and mayBeNull.
  717. * @param {function} transform - The transformation to apply to the value represented
  718. * by this {@link Bond}.
  719. * @param {number} outResolveDepth - The number of levels deep in any array
  720. * object values of the result of the transformation that {@link Bond} values
  721. * will be resolved.
  722. * @default 3
  723. * @param {*} cache - Cache information. See constructor.
  724. * @default null
  725. * @param {*} latched - Should the value be latched so that once ready it stays ready?
  726. * @default true
  727. * @param {*} mayBeNull - Should the value be allowed to be `null` such that if it ever becomes
  728. * null, it is treated as being unready?
  729. * @default false
  730. * @returns {@link Bond} - An object representing this object's value with
  731. * the function `transform` applied to it.
  732. */
  733. xform (transform, outResolveDepth = 3, cache = undefined, latched = true, mayBeNull = false) {
  734. const TransformBond = require('./transformBond');
  735. return new TransformBond(transform, [this], [], outResolveDepth, 3, cache, latched, mayBeNull);
  736. }
  737.  
  738. /**
  739. * Create a new {@link Bond} which represents this object's array value with
  740. * its elements transformed by a function.
  741. *
  742. * @example
  743. * let b = new Bond;
  744. * let t = b.mapEach(_ => _ * 2);
  745. * t.tie(console.log);
  746. * b.changed([1, 2, 3]); // logs [2, 4, 6]
  747. * b.changed([21]); // logs [42]
  748. *
  749. * @param {function} transform - The transformation to apply to each element.
  750. * @returns The new {@link Bond} object representing the element-wise
  751. * Transformation.
  752. */
  753. mapEach (transform, cache = undefined, latched = false, mayBeNull = true) {
  754. return this.map(item => item.map(transform), 3, cache, latched, mayBeNull);
  755. }
  756.  
  757. /**
  758. * Create a new {@link Bond} which represents this object's value when
  759. * subscripted.
  760. *
  761. * @example
  762. * let b = new Bond;
  763. * let t = b.sub('foo');
  764. * t.tie(console.log);
  765. * b.changed({foo: 42}); // logs 42
  766. * b.changed({foo: 69}); // logs 69
  767. *
  768. * @example
  769. * let b = new Bond;
  770. * let c = new Bond;
  771. * let t = b.sub(c);
  772. * t.tie(console.log);
  773. * b.changed([42, 4, 2]);
  774. * c.changed(0); // logs 42
  775. * c.changed(1); // logs 4
  776. * b.changed([68, 69, 70]); // logs 69
  777. *
  778. * @param {string|number} name - The field or index by which to subscript this object's
  779. * represented value. May itself be a {@link Bond}, in which case, the
  780. * resolved value is used.
  781. * @param {number} outResolveDepth - The depth in any returned structure
  782. * that a {@link Bond} may be for it to be resolved.
  783. * @returns {@link Bond} - The object representing the value which is the
  784. * value represented by this object subscripted by the value represented by
  785. * `name`.
  786. */
  787. sub (name, outResolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
  788. const TransformBond = require('./transformBond');
  789. return new TransformBond(
  790. (object, field) => object[field],
  791. [this, name],
  792. [],
  793. outResolveDepth,
  794. 3,
  795. cache
  796. );
  797. }
  798.  
  799. /**
  800. * Create a new {@link Bond} which represents the array of many objects'
  801. * representative values.
  802. *
  803. * This object will be _ready_ if and only if all objects in `list` are
  804. * themselves _ready_.
  805. *
  806. * @example
  807. * let b = new Bond;
  808. * let c = new Bond;
  809. * let t = Bond.all([b, c]);
  810. * t.tie(console.log);
  811. * b.changed(42);
  812. * c.changed(69); // logs [42, 69]
  813. * b.changed(3); // logs [3, 69]
  814. *
  815. * @example
  816. * let b = new Bond;
  817. * let c = new Bond;
  818. * let t = Bond.all(['a', {b, c}, 'd'], 2);
  819. * t.tie(console.log);
  820. * b.changed(42);
  821. * c.changed(69); // logs ['a', {b: 42, c: 69}, 'd']
  822. * b.changed(null); // logs ['a', {b: null, c: 69}, 'd']
  823. *
  824. * @param {array} list - An array of {@link Bond} objects, plain values or
  825. * structures (arrays/objects) which contain either of these.
  826. * @param {number} resolveDepth - The depth in a structure (array or object)
  827. * that a {@link Bond} may be in any of `list`'s items for it to be resolved.
  828. * @returns {@link Bond} - The object representing the value of the array of
  829. * each object's representative value in `list`.
  830. */
  831. static all (list, resolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
  832. const TransformBond = require('./transformBond');
  833. return new TransformBond((...args) => args, list, [], 3, resolveDepth, cache, latched, mayBeNull);
  834. }
  835.  
  836. /**
  837. * Create a new {@link Bond} which represents a functional transformation of
  838. * many objects' representative values.
  839. *
  840. * @example
  841. * let b = new Bond;
  842. * b.changed(23);
  843. * let c = new Bond;
  844. * c.changed(3);
  845. * let multiply = (x, y) => x * y;
  846. * // These two are exactly equivalent:
  847. * let bc = Bond.all([b, c]).map(([b, c]) => multiply(b, c));
  848. * let bc2 = Bond.mapAll([b, c], multiply);
  849. *
  850. * @param {array} list - An array of {@link Bond} objects or plain values.
  851. * @param {function} f - A function which accepts as many parameters are there
  852. * values in `list` and transforms it into a {@link Bond}, {@link Promise}
  853. * or other value.
  854. * @param {number} resolveDepth - The depth in a structure (array or object)
  855. * that a {@link Bond} may be in any of `list`'s items for it to be resolved.
  856. * @param {number} outResolveDepth - The depth in any returned structure
  857. * that a {@link Bond} may be for it to be resolved.
  858. */
  859. static mapAll (list, transform, outResolveDepth = 3, resolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
  860. const TransformBond = require('./transformBond');
  861. return new TransformBond(transform, list, [], outResolveDepth, resolveDepth, cache, latched, mayBeNull);
  862. }
  863.  
  864. // Takes a Bond which evaluates to a = [a[0], a[1], ...]
  865. // Returns Bond which evaluates to:
  866. // null iff a.length === 0
  867. // f(i, a[0])[0] iff f(i, a[0])[1] === true
  868. // fold(f(0, a[0]), a.mid(1)) otherwise
  869. /**
  870. * Lazily transforms the contents of this object's value when it is an array.
  871. *
  872. * This operates on a {@link Bond} which should represent an array. It
  873. * transforms this into a value based on a number of elements at the
  874. * beginning of that array using a recursive _reduce_ algorithm.
  875. *
  876. * The reduce algorithm works around an accumulator model. It begins with
  877. * the `init` value, and incremenetally accumulates
  878. * elements from the array by changing its value to one returned from the
  879. * `accum` function, when passed the current accumulator and the next value
  880. * from the array. The `accum` function may return a {@link Bond}, in which case it
  881. * will be resolved (using {@link Bond#then}) and that value used.
  882. *
  883. * The `accum` function returns a value (or a {@link Bond} which resolves to a value)
  884. * of an array with exactly two elements; the first is the new value for the
  885. * accumulator. The second is a boolean _early exit_ flag.
  886. *
  887. * Accumulation will continue until either there are no more elements in the
  888. * array to be processed, or until the _early exit_ flag is true, which ever
  889. * happens first.
  890. *
  891. * @param {function} accum - The reduce's accumulator function.
  892. * @param {*} init - The initialisation value for the reduce algorithm.
  893. * @returns {Bond} - A {@link Bond} representing `init` when the input array is empty,
  894. * otherwise the reduction of that array.
  895. */
  896. reduce (accum, init, cache = undefined, latched = false, mayBeNull = true) {
  897. var nextItem = function (acc, rest) {
  898. let next = rest.pop();
  899. return accum(acc, next).map(([result, finished]) =>
  900. finished
  901. ? result
  902. : rest.length > 0
  903. ? nextItem(result, rest)
  904. : null
  905. );
  906. };
  907. return this.map(array => array.length > 0 ? nextItem(init, array) : init, 3, cache, latched, mayBeNull);
  908. }
  909.  
  910. /**
  911. * Create a Promise which represents one or more {@link Bond}s.
  912. *
  913. * @example
  914. * let b = new Bond;
  915. * let p = Bond.promise([b, 42])
  916. * p.then(console.log);
  917. * b.changed(69); // logs [69, 42]
  918. * b.changed(42); // nothing.
  919. *
  920. * @param {array} list - A list of values, {Promise}s or {@link Bond}s.
  921. * @returns {Promise} - A object which resolves to an array of values
  922. * corresponding to those passed in `list`.
  923. */
  924. static promise (list) {
  925. return new Promise((resolve, reject) => {
  926. var finished = 0;
  927. var resolved = [];
  928. resolved.length = list.length;
  929.  
  930. let done = (index, value) => {
  931. // console.log(`done ${i} ${v}`);
  932. resolved[index] = value;
  933. finished++;
  934. // console.log(`finished ${finished}; l.length ${l.length}`);
  935. if (finished === resolved.length) {
  936. // console.log(`resolving with ${l}`);
  937. resolve(resolved);
  938. }
  939. };
  940.  
  941. list.forEach((unresolvedObject, index) => {
  942. if (Bond.instanceOf(unresolvedObject)) {
  943. // unresolvedObject is a Bond.
  944. unresolvedObject.then(value => done(index, value));
  945. } else if (unresolvedObject instanceof Promise) {
  946. // unresolvedObject is a Promise.
  947. unresolvedObject.then(value => done(index, value), reject);
  948. } else {
  949. // unresolvedObject is actually just a normal value.
  950. done(index, unresolvedObject);
  951. }
  952. });
  953. });
  954. }
  955.  
  956. /**
  957. * Duck-typed alternative to `instanceof Bond`, when multiple instantiations
  958. * of `Bond` may be available.
  959. */
  960. static instanceOf (b) {
  961. return (
  962. typeof (b) === 'object' &&
  963. b !== null &&
  964. typeof (b.reset) === 'function' &&
  965. typeof (b.changed) === 'function'
  966. );
  967. }
  968. }
  969.  
  970. Bond.backupStorage = {};
  971. Bond.cache = new BondCache(Bond.backupStorage);
  972.  
  973. module.exports = Bond;