0af0fc51df1fdb42ed7c0c368bc9e23d13e7e8991ec5b9a4a29fc480324c599d765f166df994012ff7cc78aa3489ea239cbcf9105abd6a3df4937a9e87888b 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /* @prettier */
  2. import { SchedulerLike } from '../types';
  3. import { Observable } from '../Observable';
  4. import { bindCallbackInternals } from './bindCallbackInternals';
  5. export function bindCallback(
  6. callbackFunc: (...args: any[]) => void,
  7. resultSelector: (...args: any[]) => any,
  8. scheduler?: SchedulerLike
  9. ): (...args: any[]) => Observable<any>;
  10. // args is the arguments array and we push the callback on the rest tuple since the rest parameter must be last (only item) in a parameter list
  11. export function bindCallback<A extends readonly unknown[], R extends readonly unknown[]>(
  12. callbackFunc: (...args: [...A, (...res: R) => void]) => void,
  13. schedulerLike?: SchedulerLike
  14. ): (...arg: A) => Observable<R extends [] ? void : R extends [any] ? R[0] : R>;
  15. /**
  16. * Converts a callback API to a function that returns an Observable.
  17. *
  18. * <span class="informal">Give it a function `f` of type `f(x, callback)` and
  19. * it will return a function `g` that when called as `g(x)` will output an
  20. * Observable.</span>
  21. *
  22. * `bindCallback` is not an operator because its input and output are not
  23. * Observables. The input is a function `func` with some parameters. The
  24. * last parameter must be a callback function that `func` calls when it is
  25. * done.
  26. *
  27. * The output of `bindCallback` is a function that takes the same parameters
  28. * as `func`, except the last one (the callback). When the output function
  29. * is called with arguments it will return an Observable. If function `func`
  30. * calls its callback with one argument, the Observable will emit that value.
  31. * If on the other hand the callback is called with multiple values the resulting
  32. * Observable will emit an array with said values as arguments.
  33. *
  34. * It is **very important** to remember that input function `func` is not called
  35. * when the output function is, but rather when the Observable returned by the output
  36. * function is subscribed. This means if `func` makes an AJAX request, that request
  37. * will be made every time someone subscribes to the resulting Observable, but not before.
  38. *
  39. * The last optional parameter - `scheduler` - can be used to control when the call
  40. * to `func` happens after someone subscribes to Observable, as well as when results
  41. * passed to callback will be emitted. By default, the subscription to an Observable calls `func`
  42. * synchronously, but using {@link asyncScheduler} as the last parameter will defer the call to `func`,
  43. * just like wrapping the call in `setTimeout` with a timeout of `0` would. If you were to use the async Scheduler
  44. * and call `subscribe` on the output Observable, all function calls that are currently executing
  45. * will end before `func` is invoked.
  46. *
  47. * By default, results passed to the callback are emitted immediately after `func` invokes the callback.
  48. * In particular, if the callback is called synchronously, then the subscription of the resulting Observable
  49. * will call the `next` function synchronously as well. If you want to defer that call,
  50. * you may use {@link asyncScheduler} just as before. This means that by using `Scheduler.async` you can
  51. * ensure that `func` always calls its callback asynchronously, thus avoiding terrifying Zalgo.
  52. *
  53. * Note that the Observable created by the output function will always emit a single value
  54. * and then complete immediately. If `func` calls the callback multiple times, values from subsequent
  55. * calls will not appear in the stream. If you need to listen for multiple calls,
  56. * you probably want to use {@link fromEvent} or {@link fromEventPattern} instead.
  57. *
  58. * If `func` depends on some context (`this` property) and is not already bound, the context of `func`
  59. * will be the context that the output function has at call time. In particular, if `func`
  60. * is called as a method of some object and if `func` is not already bound, in order to preserve the context
  61. * it is recommended that the context of the output function is set to that object as well.
  62. *
  63. * If the input function calls its callback in the "node style" (i.e. first argument to callback is
  64. * optional error parameter signaling whether the call failed or not), {@link bindNodeCallback}
  65. * provides convenient error handling and probably is a better choice.
  66. * `bindCallback` will treat such functions the same as any other and error parameters
  67. * (whether passed or not) will always be interpreted as regular callback argument.
  68. *
  69. * ## Examples
  70. *
  71. * Convert jQuery's getJSON to an Observable API
  72. *
  73. * ```ts
  74. * import { bindCallback } from 'rxjs';
  75. * import * as jQuery from 'jquery';
  76. *
  77. * // Suppose we have jQuery.getJSON('/my/url', callback)
  78. * const getJSONAsObservable = bindCallback(jQuery.getJSON);
  79. * const result = getJSONAsObservable('/my/url');
  80. * result.subscribe(x => console.log(x), e => console.error(e));
  81. * ```
  82. *
  83. * Receive an array of arguments passed to a callback
  84. *
  85. * ```ts
  86. * import { bindCallback } from 'rxjs';
  87. *
  88. * const someFunction = (n, s, cb) => {
  89. * cb(n, s, { someProperty: 'someValue' });
  90. * };
  91. *
  92. * const boundSomeFunction = bindCallback(someFunction);
  93. * boundSomeFunction(5, 'some string').subscribe((values) => {
  94. * console.log(values); // [5, 'some string', {someProperty: 'someValue'}]
  95. * });
  96. * ```
  97. *
  98. * Compare behaviour with and without `asyncScheduler`
  99. *
  100. * ```ts
  101. * import { bindCallback, asyncScheduler } from 'rxjs';
  102. *
  103. * function iCallMyCallbackSynchronously(cb) {
  104. * cb();
  105. * }
  106. *
  107. * const boundSyncFn = bindCallback(iCallMyCallbackSynchronously);
  108. * const boundAsyncFn = bindCallback(iCallMyCallbackSynchronously, null, asyncScheduler);
  109. *
  110. * boundSyncFn().subscribe(() => console.log('I was sync!'));
  111. * boundAsyncFn().subscribe(() => console.log('I was async!'));
  112. * console.log('This happened...');
  113. *
  114. * // Logs:
  115. * // I was sync!
  116. * // This happened...
  117. * // I was async!
  118. * ```
  119. *
  120. * Use `bindCallback` on an object method
  121. *
  122. * ```ts
  123. * import { bindCallback } from 'rxjs';
  124. *
  125. * const boundMethod = bindCallback(someObject.methodWithCallback);
  126. * boundMethod
  127. * .call(someObject) // make sure methodWithCallback has access to someObject
  128. * .subscribe(subscriber);
  129. * ```
  130. *
  131. * @see {@link bindNodeCallback}
  132. * @see {@link from}
  133. *
  134. * @param callbackFunc A function with a callback as the last parameter.
  135. * @param resultSelector A mapping function used to transform callback events.
  136. * @param scheduler The scheduler on which to schedule the callbacks.
  137. * @return A function which returns the Observable that delivers the same
  138. * values the callback would deliver.
  139. */
  140. export function bindCallback(
  141. callbackFunc: (...args: [...any[], (...res: any) => void]) => void,
  142. resultSelector?: ((...args: any[]) => any) | SchedulerLike,
  143. scheduler?: SchedulerLike
  144. ): (...args: any[]) => Observable<unknown> {
  145. return bindCallbackInternals(false, callbackFunc, resultSelector, scheduler);
  146. }