38284a988a6d00c82636ea3fc26a36a33a7a84163876a476048948e109ceccf6166ede9496bf1de43f37e83d72c059fcb7455101a8278e6949aeb542735e3c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import { SchedulerLike, SchedulerAction } from '../types';
  2. import { Observable } from '../Observable';
  3. import { AsyncSubject } from '../AsyncSubject';
  4. import { Subscriber } from '../Subscriber';
  5. import { map } from '../operators/map';
  6. import { canReportError } from '../util/canReportError';
  7. import { isArray } from '../util/isArray';
  8. import { isScheduler } from '../util/isScheduler';
  9. // tslint:disable:max-line-length
  10. /** @deprecated resultSelector is no longer supported, use a mapping function. */
  11. export function bindCallback(callbackFunc: Function, resultSelector: Function, scheduler?: SchedulerLike): (...args: any[]) => Observable<any>;
  12. export function bindCallback<R1, R2, R3, R4>(callbackFunc: (callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): () => Observable<any[]>;
  13. export function bindCallback<R1, R2, R3>(callbackFunc: (callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): () => Observable<[R1, R2, R3]>;
  14. export function bindCallback<R1, R2>(callbackFunc: (callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): () => Observable<[R1, R2]>;
  15. export function bindCallback<R1>(callbackFunc: (callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): () => Observable<R1>;
  16. export function bindCallback(callbackFunc: (callback: () => any) => any, scheduler?: SchedulerLike): () => Observable<void>;
  17. export function bindCallback<A1, R1, R2, R3, R4>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<any[]>;
  18. export function bindCallback<A1, R1, R2, R3>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<[R1, R2, R3]>;
  19. export function bindCallback<A1, R1, R2>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<[R1, R2]>;
  20. export function bindCallback<A1, R1>(callbackFunc: (arg1: A1, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<R1>;
  21. export function bindCallback<A1>(callbackFunc: (arg1: A1, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<void>;
  22. export function bindCallback<A1, A2, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<any[]>;
  23. export function bindCallback<A1, A2, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<[R1, R2, R3]>;
  24. export function bindCallback<A1, A2, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<[R1, R2]>;
  25. export function bindCallback<A1, A2, R1>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<R1>;
  26. export function bindCallback<A1, A2>(callbackFunc: (arg1: A1, arg2: A2, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<void>;
  27. export function bindCallback<A1, A2, A3, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<any[]>;
  28. export function bindCallback<A1, A2, A3, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<[R1, R2, R3]>;
  29. export function bindCallback<A1, A2, A3, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<[R1, R2]>;
  30. export function bindCallback<A1, A2, A3, R1>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<R1>;
  31. export function bindCallback<A1, A2, A3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<void>;
  32. export function bindCallback<A1, A2, A3, A4, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<any[]>;
  33. export function bindCallback<A1, A2, A3, A4, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<[R1, R2, R3]>;
  34. export function bindCallback<A1, A2, A3, A4, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<[R1, R2]>;
  35. export function bindCallback<A1, A2, A3, A4, R1>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<R1>;
  36. export function bindCallback<A1, A2, A3, A4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<void>;
  37. export function bindCallback<A1, A2, A3, A4, A5, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<any[]>;
  38. export function bindCallback<A1, A2, A3, A4, A5, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<[R1, R2, R3]>;
  39. export function bindCallback<A1, A2, A3, A4, A5, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<[R1, R2]>;
  40. export function bindCallback<A1, A2, A3, A4, A5, R1>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<R1>;
  41. export function bindCallback<A1, A2, A3, A4, A5>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<void>;
  42. export function bindCallback<A, R>(callbackFunc: (...args: Array<A | ((result: R) => any)>) => any, scheduler?: SchedulerLike): (...args: A[]) => Observable<R>;
  43. export function bindCallback<A, R>(callbackFunc: (...args: Array<A | ((...results: R[]) => any)>) => any, scheduler?: SchedulerLike): (...args: A[]) => Observable<R[]>;
  44. export function bindCallback(callbackFunc: Function, scheduler?: SchedulerLike): (...args: any[]) => Observable<any>;
  45. // tslint:enable:max-line-length
  46. /**
  47. * Converts a callback API to a function that returns an Observable.
  48. *
  49. * <span class="informal">Give it a function `f` of type `f(x, callback)` and
  50. * it will return a function `g` that when called as `g(x)` will output an
  51. * Observable.</span>
  52. *
  53. * `bindCallback` is not an operator because its input and output are not
  54. * Observables. The input is a function `func` with some parameters. The
  55. * last parameter must be a callback function that `func` calls when it is
  56. * done.
  57. *
  58. * The output of `bindCallback` is a function that takes the same parameters
  59. * as `func`, except the last one (the callback). When the output function
  60. * is called with arguments it will return an Observable. If function `func`
  61. * calls its callback with one argument, the Observable will emit that value.
  62. * If on the other hand the callback is called with multiple values the resulting
  63. * Observable will emit an array with said values as arguments.
  64. *
  65. * It is **very important** to remember that input function `func` is not called
  66. * when the output function is, but rather when the Observable returned by the output
  67. * function is subscribed. This means if `func` makes an AJAX request, that request
  68. * will be made every time someone subscribes to the resulting Observable, but not before.
  69. *
  70. * The last optional parameter - `scheduler` - can be used to control when the call
  71. * to `func` happens after someone subscribes to Observable, as well as when results
  72. * passed to callback will be emitted. By default, the subscription to an Observable calls `func`
  73. * synchronously, but using {@link asyncScheduler} as the last parameter will defer the call to `func`,
  74. * just like wrapping the call in `setTimeout` with a timeout of `0` would. If you were to use the async Scheduler
  75. * and call `subscribe` on the output Observable, all function calls that are currently executing
  76. * will end before `func` is invoked.
  77. *
  78. * By default, results passed to the callback are emitted immediately after `func` invokes the callback.
  79. * In particular, if the callback is called synchronously, then the subscription of the resulting Observable
  80. * will call the `next` function synchronously as well. If you want to defer that call,
  81. * you may use {@link asyncScheduler} just as before. This means that by using `Scheduler.async` you can
  82. * ensure that `func` always calls its callback asynchronously, thus avoiding terrifying Zalgo.
  83. *
  84. * Note that the Observable created by the output function will always emit a single value
  85. * and then complete immediately. If `func` calls the callback multiple times, values from subsequent
  86. * calls will not appear in the stream. If you need to listen for multiple calls,
  87. * you probably want to use {@link fromEvent} or {@link fromEventPattern} instead.
  88. *
  89. * If `func` depends on some context (`this` property) and is not already bound, the context of `func`
  90. * will be the context that the output function has at call time. In particular, if `func`
  91. * is called as a method of some objec and if `func` is not already bound, in order to preserve the context
  92. * it is recommended that the context of the output function is set to that object as well.
  93. *
  94. * If the input function calls its callback in the "node style" (i.e. first argument to callback is
  95. * optional error parameter signaling whether the call failed or not), {@link bindNodeCallback}
  96. * provides convenient error handling and probably is a better choice.
  97. * `bindCallback` will treat such functions the same as any other and error parameters
  98. * (whether passed or not) will always be interpreted as regular callback argument.
  99. *
  100. * ## Examples
  101. *
  102. * ### Convert jQuery's getJSON to an Observable API
  103. * ```ts
  104. * import { bindCallback } from 'rxjs';
  105. * import * as jQuery from 'jquery';
  106. *
  107. * // Suppose we have jQuery.getJSON('/my/url', callback)
  108. * const getJSONAsObservable = bindCallback(jQuery.getJSON);
  109. * const result = getJSONAsObservable('/my/url');
  110. * result.subscribe(x => console.log(x), e => console.error(e));
  111. * ```
  112. *
  113. * ### Receive an array of arguments passed to a callback
  114. * ```ts
  115. * import { bindCallback } from 'rxjs';
  116. *
  117. * const someFunction = (a, b, c) => {
  118. * console.log(a); // 5
  119. * console.log(b); // 'some string'
  120. * console.log(c); // {someProperty: 'someValue'}
  121. * };
  122. *
  123. * const boundSomeFunction = bindCallback(someFunction);
  124. * boundSomeFunction().subscribe(values => {
  125. * console.log(values) // [5, 'some string', {someProperty: 'someValue'}]
  126. * });
  127. * ```
  128. *
  129. * ### Compare behaviour with and without async Scheduler
  130. * ```ts
  131. * import { bindCallback } from 'rxjs';
  132. *
  133. * function iCallMyCallbackSynchronously(cb) {
  134. * cb();
  135. * }
  136. *
  137. * const boundSyncFn = bindCallback(iCallMyCallbackSynchronously);
  138. * const boundAsyncFn = bindCallback(iCallMyCallbackSynchronously, null, Rx.Scheduler.async);
  139. *
  140. * boundSyncFn().subscribe(() => console.log('I was sync!'));
  141. * boundAsyncFn().subscribe(() => console.log('I was async!'));
  142. * console.log('This happened...');
  143. *
  144. * // Logs:
  145. * // I was sync!
  146. * // This happened...
  147. * // I was async!
  148. * ```
  149. *
  150. * ### Use bindCallback on an object method
  151. * ```ts
  152. * import { bindCallback } from 'rxjs';
  153. *
  154. * const boundMethod = bindCallback(someObject.methodWithCallback);
  155. * boundMethod.call(someObject) // make sure methodWithCallback has access to someObject
  156. * .subscribe(subscriber);
  157. * ```
  158. *
  159. * @see {@link bindNodeCallback}
  160. * @see {@link from}
  161. *
  162. * @param {function} func A function with a callback as the last parameter.
  163. * @param {SchedulerLike} [scheduler] The scheduler on which to schedule the
  164. * callbacks.
  165. * @return {function(...params: *): Observable} A function which returns the
  166. * Observable that delivers the same values the callback would deliver.
  167. * @name bindCallback
  168. */
  169. export function bindCallback<T>(
  170. callbackFunc: Function,
  171. resultSelector?: Function|SchedulerLike,
  172. scheduler?: SchedulerLike
  173. ): (...args: any[]) => Observable<T> {
  174. if (resultSelector) {
  175. if (isScheduler(resultSelector)) {
  176. scheduler = resultSelector;
  177. } else {
  178. // DEPRECATED PATH
  179. return (...args: any[]) => bindCallback(callbackFunc, scheduler)(...args).pipe(
  180. map((args) => isArray(args) ? resultSelector(...args) : resultSelector(args)),
  181. );
  182. }
  183. }
  184. return function (this: any, ...args: any[]): Observable<T> {
  185. const context = this;
  186. let subject: AsyncSubject<T>;
  187. const params = {
  188. context,
  189. subject,
  190. callbackFunc,
  191. scheduler,
  192. };
  193. return new Observable<T>(subscriber => {
  194. if (!scheduler) {
  195. if (!subject) {
  196. subject = new AsyncSubject<T>();
  197. const handler = (...innerArgs: any[]) => {
  198. subject.next(innerArgs.length <= 1 ? innerArgs[0] : innerArgs);
  199. subject.complete();
  200. };
  201. try {
  202. callbackFunc.apply(context, [...args, handler]);
  203. } catch (err) {
  204. if (canReportError(subject)) {
  205. subject.error(err);
  206. } else {
  207. console.warn(err);
  208. }
  209. }
  210. }
  211. return subject.subscribe(subscriber);
  212. } else {
  213. const state: DispatchState<T> = {
  214. args, subscriber, params,
  215. };
  216. return scheduler.schedule<DispatchState<T>>(dispatch, 0, state);
  217. }
  218. });
  219. };
  220. }
  221. interface DispatchState<T> {
  222. args: any[];
  223. subscriber: Subscriber<T>;
  224. params: ParamsContext<T>;
  225. }
  226. interface ParamsContext<T> {
  227. callbackFunc: Function;
  228. scheduler: SchedulerLike;
  229. context: any;
  230. subject: AsyncSubject<T>;
  231. }
  232. function dispatch<T>(this: SchedulerAction<DispatchState<T>>, state: DispatchState<T>) {
  233. const self = this;
  234. const { args, subscriber, params } = state;
  235. const { callbackFunc, context, scheduler } = params;
  236. let { subject } = params;
  237. if (!subject) {
  238. subject = params.subject = new AsyncSubject<T>();
  239. const handler = (...innerArgs: any[]) => {
  240. const value = innerArgs.length <= 1 ? innerArgs[0] : innerArgs;
  241. this.add(scheduler.schedule<NextState<T>>(dispatchNext, 0, { value, subject }));
  242. };
  243. try {
  244. callbackFunc.apply(context, [...args, handler]);
  245. } catch (err) {
  246. subject.error(err);
  247. }
  248. }
  249. this.add(subject.subscribe(subscriber));
  250. }
  251. interface NextState<T> {
  252. subject: AsyncSubject<T>;
  253. value: T;
  254. }
  255. function dispatchNext<T>(this: SchedulerAction<NextState<T>>, state: NextState<T>) {
  256. const { value, subject } = state;
  257. subject.next(value);
  258. subject.complete();
  259. }
  260. interface ErrorState<T> {
  261. subject: AsyncSubject<T>;
  262. err: any;
  263. }
  264. function dispatchError<T>(this: SchedulerAction<ErrorState<T>>, state: ErrorState<T>) {
  265. const { err, subject } = state;
  266. subject.error(err);
  267. }