In RxJS, what is the difference between of([1, 2, 3]) and from([1, 2, 3]), and what inputs does from() accept?
answer
- arguments versus contents
- one array value or three numbers
- ObservableInput
- promises, iterables, streams
basics
~20 sof([1, 2, 3]) emits the array as one value; from([1, 2, 3]) emits 1, 2 and 3 separately. from() converts any ObservableInput - arrays, iterables, promises, async iterables, ReadableStreams or Observables - into an Observable.
solid answer
~40 s`of(...values)` emits each **argument** as-is, then completes, so `of([1, 2, 3])` emits a single array value and `of(1, 2, 3)` emits three numbers. `from(input)` **unpacks** one input: `from([1, 2, 3])` emits 1, 2, 3 and completes. `from()` accepts any `ObservableInput`: an Observable (returned as-is), an object implementing `Symbol.observable`, an array or array-like (so a string emits its characters), a Promise or other thenable, an async iterable, an iterable such as a `Map` or generator, or a `ReadableStream`. Anything else throws a `TypeError` when `from()` is called. `of` and `from` over arrays or iterables emit synchronously during `subscribe()`; `from(promise)` emits later, when the promise resolves.
code
ts · 17 linesimport { from, of } from 'rxjs';
of([1, 2, 3]).subscribe((v) => console.log('of:', v));
// of: [1, 2, 3]
from([1, 2, 3]).subscribe((v) => console.log('from:', v));
// from: 1
// from: 2
// from: 3
from(new Map([['a', 1]])).subscribe((v) => console.log('map entry:', v));
// map entry: ['a', 1]
from(Promise.resolve(42)).subscribe((v) => console.log('promise:', v));
console.log('after subscribe');
// after subscribe
// promise: 42go deeper
Recall that of emits its arguments and from unpacks one input, and give the array example both ways.
List the ObservableInput kinds from() accepts, and say which ones emit synchronously and which emit when a promise resolves.
Watch for code that relies on from(promise) being lazy, and for per-item logic silently receiving whole arrays because of() was used.
Encourage APIs that accept ObservableInput rather than Observable only, so callers can pass arrays or promises without wrapping them.
## Two creation functions, two jobs RxJS's **creation functions** build an `Observable` from something that already exists. `of` and `from` are the two most used, and they are easy to confuse because both can take an array. - **`of(...values)`** treats each argument as one value. It emits the arguments in order, then completes. - **`from(input)`** takes a single input and converts it, **unpacking** whatever it contains. | Expression | Emits | Then | |---|---|---| | `of(1, 2, 3)` | `1`, `2`, `3` | complete | | `of([1, 2, 3])` | `[1, 2, 3]` (one value) | complete | | `from([1, 2, 3])` | `1`, `2`, `3` | complete | | `of()` | nothing | complete | | `from('abc')` | `'a'`, `'b'`, `'c'` | complete | Under the hood, `of` simply calls `from` on its argument list, which is why `of(1, 2, 3)` and `from([1, 2, 3])` behave the same. ## What from() accepts `from()` accepts the type RxJS calls **`ObservableInput`**. RxJS 7 checks the input in this order: 1. an RxJS `Observable` - returned unchanged; 2. an object implementing **`Symbol.observable`** (an interop observable from another library); 3. an **array-like** value - arrays, `arguments`, typed arrays and strings; 4. a **Promise** or any thenable - it emits the resolved value then completes, or errors on rejection; 5. an **async iterable** - such as an async generator; 6. an **iterable** - a `Set`, a `Map` (yielding `[key, value]` pairs) or a generator; 7. a **ReadableStream**-like object. If none of these match - a plain object, a number, `null` - `from()` throws a `TypeError` whose message reads "You provided ... where a stream was expected". The check happens when `from()` is **called**, not when the result is subscribed, so the error surfaces at the call site. The same `ObservableInput` type is what operators accept wherever they need an inner source, which is why a function that returns a Promise or an array can be used almost anywhere RxJS expects an Observable. ## Timing and completion Both functions **complete** once the input is exhausted, but they differ in when values arrive: - `of(...)`, `from(array)` and `from(iterable)` emit **synchronously**: every value and the completion are delivered before `subscribe()` returns. - `from(promise)` emits **asynchronously**, when the promise's reaction runs - even for a promise that has already resolved. - `from(asyncIterable)` and `from(readableStream)` emit asynchronously as items become available. The array loop also checks whether the subscriber has closed before each value, so an operator such as `take(2)` downstream stops a large array early instead of walking all of it. ## Common mistakes - Writing `of(items)` when the code needs one emission per item. The subscriber receives the whole array once, and per-item operators never see individual items. - Writing `from(items)` when the code needs the array as a single value - for example, when feeding a list into a stream of "current list" states. - Passing a plain object to `from()` and expecting its properties to be emitted. Objects are not iterable; use `from(Object.entries(obj))` instead. - Believing `from(promise)` makes the promise lazy. The promise has already started; `from` only adapts its result. Deferring the promise's creation is `defer`'s job. ## from() as a normaliser Because `from()` returns an RxJS `Observable` unchanged, it is cheap to call on something that might already be one. That makes it a handy normaliser at API boundaries: - a helper that accepts `ObservableInput<T>` can call `from(input)` once and then use operators, whatever the caller passed; - an Observable from another library that implements `Symbol.observable` is adapted into an RxJS one, so RxJS operators can be applied to it; - a callback that sometimes returns an array and sometimes a promise can be handled by the same code path. ## Deprecated forms to recognise Older code passes a scheduler as the last argument - `of(1, 2, 3, asyncScheduler)` or `from(array, scheduler)`. That argument is deprecated since RxJS 6.5 and scheduled for removal in v8; the replacement is `scheduled(input, scheduler)`. Explicit type arguments on `of<T>()` with no values are also deprecated. ## In an interview The one-line answer is "`of` emits its arguments, `from` unpacks its input". A strong candidate adds the list of `ObservableInput` types, notes that `from(promise)` is asynchronous while `of` and `from(array)` are synchronous, and knows that `from()` on an unsupported value throws immediately rather than erroring through the stream.
- In RxJS, what happens if you call from() with a plain object such as { a: 1 }?It throws a `TypeError` immediately, at the `from()` call, because a plain object is not an `ObservableInput`: it is not array-like, not a thenable and not iterable. The message says an Observable, Promise, ReadableStream, Array, AsyncIterable or Iterable was expected. To stream its properties, pass `Object.entries(obj)` to `from()` instead.
- In RxJS 7, how do you make of(1, 2, 3) emit asynchronously now that the scheduler argument is deprecated?Use `scheduled([1, 2, 3], asyncScheduler)`, which RxJS added as the replacement for the scheduler argument on `of` and `from`. Each value is then delivered through the scheduler instead of inside the `subscribe()` call. How the individual schedulers differ is a separate topic.
saying these in an interview costs you the question
- of([1, 2, 3]) emits 1, 2 and 3 as separate values.
- from() over a plain object emits each property.
- from(promise) makes the promise lazy, so it starts on subscribe.
- of and from always emit asynchronously.
- from() on an unsupported value errors through the stream when subscribed.