first.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import { Subscriber } from '../Subscriber';
  2. import { EmptyError } from '../util/EmptyError';
  3. /**
  4. * Emits only the first value (or the first value that meets some condition)
  5. * emitted by the source Observable.
  6. *
  7. * <span class="informal">Emits only the first value. Or emits only the first
  8. * value that passes some test.</span>
  9. *
  10. * <img src="./img/first.png" width="100%">
  11. *
  12. * If called with no arguments, `first` emits the first value of the source
  13. * Observable, then completes. If called with a `predicate` function, `first`
  14. * emits the first value of the source that matches the specified condition. It
  15. * may also take a `resultSelector` function to produce the output value from
  16. * the input value, and a `defaultValue` to emit in case the source completes
  17. * before it is able to emit a valid value. Throws an error if `defaultValue`
  18. * was not provided and a matching element is not found.
  19. *
  20. * @example <caption>Emit only the first click that happens on the DOM</caption>
  21. * var clicks = Rx.Observable.fromEvent(document, 'click');
  22. * var result = clicks.first();
  23. * result.subscribe(x => console.log(x));
  24. *
  25. * @example <caption>Emits the first click that happens on a DIV</caption>
  26. * var clicks = Rx.Observable.fromEvent(document, 'click');
  27. * var result = clicks.first(ev => ev.target.tagName === 'DIV');
  28. * result.subscribe(x => console.log(x));
  29. *
  30. * @see {@link filter}
  31. * @see {@link find}
  32. * @see {@link take}
  33. *
  34. * @throws {EmptyError} Delivers an EmptyError to the Observer's `error`
  35. * callback if the Observable completes before any `next` notification was sent.
  36. *
  37. * @param {function(value: T, index: number, source: Observable<T>): boolean} [predicate]
  38. * An optional function called with each item to test for condition matching.
  39. * @param {function(value: T, index: number): R} [resultSelector] A function to
  40. * produce the value on the output Observable based on the values
  41. * and the indices of the source Observable. The arguments passed to this
  42. * function are:
  43. * - `value`: the value that was emitted on the source.
  44. * - `index`: the "index" of the value from the source.
  45. * @param {R} [defaultValue] The default value emitted in case no valid value
  46. * was found on the source.
  47. * @return {Observable<T|R>} An Observable of the first item that matches the
  48. * condition.
  49. * @method first
  50. * @owner Observable
  51. */
  52. export function first(predicate, resultSelector, defaultValue) {
  53. return (source) => source.lift(new FirstOperator(predicate, resultSelector, defaultValue, source));
  54. }
  55. class FirstOperator {
  56. constructor(predicate, resultSelector, defaultValue, source) {
  57. this.predicate = predicate;
  58. this.resultSelector = resultSelector;
  59. this.defaultValue = defaultValue;
  60. this.source = source;
  61. }
  62. call(observer, source) {
  63. return source.subscribe(new FirstSubscriber(observer, this.predicate, this.resultSelector, this.defaultValue, this.source));
  64. }
  65. }
  66. /**
  67. * We need this JSDoc comment for affecting ESDoc.
  68. * @ignore
  69. * @extends {Ignored}
  70. */
  71. class FirstSubscriber extends Subscriber {
  72. constructor(destination, predicate, resultSelector, defaultValue, source) {
  73. super(destination);
  74. this.predicate = predicate;
  75. this.resultSelector = resultSelector;
  76. this.defaultValue = defaultValue;
  77. this.source = source;
  78. this.index = 0;
  79. this.hasCompleted = false;
  80. this._emitted = false;
  81. }
  82. _next(value) {
  83. const index = this.index++;
  84. if (this.predicate) {
  85. this._tryPredicate(value, index);
  86. }
  87. else {
  88. this._emit(value, index);
  89. }
  90. }
  91. _tryPredicate(value, index) {
  92. let result;
  93. try {
  94. result = this.predicate(value, index, this.source);
  95. }
  96. catch (err) {
  97. this.destination.error(err);
  98. return;
  99. }
  100. if (result) {
  101. this._emit(value, index);
  102. }
  103. }
  104. _emit(value, index) {
  105. if (this.resultSelector) {
  106. this._tryResultSelector(value, index);
  107. return;
  108. }
  109. this._emitFinal(value);
  110. }
  111. _tryResultSelector(value, index) {
  112. let result;
  113. try {
  114. result = this.resultSelector(value, index);
  115. }
  116. catch (err) {
  117. this.destination.error(err);
  118. return;
  119. }
  120. this._emitFinal(result);
  121. }
  122. _emitFinal(value) {
  123. const destination = this.destination;
  124. if (!this._emitted) {
  125. this._emitted = true;
  126. destination.next(value);
  127. destination.complete();
  128. this.hasCompleted = true;
  129. }
  130. }
  131. _complete() {
  132. const destination = this.destination;
  133. if (!this.hasCompleted && typeof this.defaultValue !== 'undefined') {
  134. destination.next(this.defaultValue);
  135. destination.complete();
  136. }
  137. else if (!this.hasCompleted) {
  138. destination.error(new EmptyError);
  139. }
  140. }
  141. }
  142. //# sourceMappingURL=first.js.map