async-replace.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * @license
  3. * Copyright 2017 Google LLC
  4. * SPDX-License-Identifier: BSD-3-Clause
  5. */
  6. import { noChange } from '../lit-html.js';
  7. import { directive } from '../directive.js';
  8. import { AsyncDirective } from '../async-directive.js';
  9. import { Pauser, PseudoWeakRef, forAwaitOf } from './private-async-helpers.js';
  10. export class AsyncReplaceDirective extends AsyncDirective {
  11. constructor() {
  12. super(...arguments);
  13. this.__weakThis = new PseudoWeakRef(this);
  14. this.__pauser = new Pauser();
  15. }
  16. // @ts-expect-error value not used, but we want a nice parameter for docs
  17. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  18. render(value, _mapper) {
  19. return noChange;
  20. }
  21. update(_part, [value, mapper]) {
  22. // If our initial render occurs while disconnected, ensure that the pauser
  23. // and weakThis are in the disconnected state
  24. if (!this.isConnected) {
  25. this.disconnected();
  26. }
  27. // If we've already set up this particular iterable, we don't need
  28. // to do anything.
  29. if (value === this.__value) {
  30. return;
  31. }
  32. this.__value = value;
  33. let i = 0;
  34. const { __weakThis: weakThis, __pauser: pauser } = this;
  35. // Note, the callback avoids closing over `this` so that the directive
  36. // can be gc'ed before the promise resolves; instead `this` is retrieved
  37. // from `weakThis`, which can break the hard reference in the closure when
  38. // the directive disconnects
  39. forAwaitOf(value, async (v) => {
  40. // The while loop here handles the case that the connection state
  41. // thrashes, causing the pauser to resume and then get re-paused
  42. while (pauser.get()) {
  43. await pauser.get();
  44. }
  45. // If the callback gets here and there is no `this`, it means that the
  46. // directive has been disconnected and garbage collected and we don't
  47. // need to do anything else
  48. const _this = weakThis.deref();
  49. if (_this !== undefined) {
  50. // Check to make sure that value is the still the current value of
  51. // the part, and if not bail because a new value owns this part
  52. if (_this.__value !== value) {
  53. return false;
  54. }
  55. // As a convenience, because functional-programming-style
  56. // transforms of iterables and async iterables requires a library,
  57. // we accept a mapper function. This is especially convenient for
  58. // rendering a template for each item.
  59. if (mapper !== undefined) {
  60. v = mapper(v, i);
  61. }
  62. _this.commitValue(v, i);
  63. i++;
  64. }
  65. return true;
  66. });
  67. return noChange;
  68. }
  69. // Override point for AsyncAppend to append rather than replace
  70. commitValue(value, _index) {
  71. this.setValue(value);
  72. }
  73. disconnected() {
  74. this.__weakThis.disconnect();
  75. this.__pauser.pause();
  76. }
  77. reconnected() {
  78. this.__weakThis.reconnect(this);
  79. this.__pauser.resume();
  80. }
  81. }
  82. /**
  83. * A directive that renders the items of an async iterable[1], replacing
  84. * previous values with new values, so that only one value is ever rendered
  85. * at a time. This directive may be used in any expression type.
  86. *
  87. * Async iterables are objects with a `[Symbol.asyncIterator]` method, which
  88. * returns an iterator who's `next()` method returns a Promise. When a new
  89. * value is available, the Promise resolves and the value is rendered to the
  90. * Part controlled by the directive. If another value other than this
  91. * directive has been set on the Part, the iterable will no longer be listened
  92. * to and new values won't be written to the Part.
  93. *
  94. * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
  95. *
  96. * @param value An async iterable
  97. * @param mapper An optional function that maps from (value, index) to another
  98. * value. Useful for generating templates for each item in the iterable.
  99. */
  100. export const asyncReplace = directive(AsyncReplaceDirective);
  101. //# sourceMappingURL=async-replace.js.map