Susan Harkins (401) [Avatar] Offline
#1
Please post errors found in the published version of RxJS in Action here. If necessary, we'll publish a comprehensive list for everyone's convenience. Thank you!

Susan Harkins
Errata Editor
468331 (1) [Avatar] Offline
#2
pg 33:
will be extremely brittle and hard to maintain and will cause to you to come in to work

pg 35:
As you’ll see learn later on, RxJS streams follow this same functor-like design.
afbroman (6) [Avatar] Offline
#3
pg 104:

Line 6 of the second code block should have a "let":

Instead of
for(result of testData)
it should be
for(let result of testData)
508741 (2) [Avatar] Offline
#4
Page 158 PDF - Missing "be"

Now, let’s tie the SessionDisposable object to a stream. For
this example, you’ll (be) use the using() operator to construct an observable sequence
that depends on an object whose lifetime is tied to the resulting observable sequence’s
lifetime; in other words, you’ll make one stream dependent on another one.
508741 (2) [Avatar] Offline
#5
''
457562 (3) [Avatar] Offline
#6
Page 131, paragraph 1, sentence 1: The first stream is delayed.

Intuitively, because the second first stream is delayed by 3 seconds, you’d expect to see the
letters a, b, and c emitted first.
457562 (3) [Avatar] Offline
#7
Page 145, paragraph 1, sentence 2: Should be every rather than everything.

Now, you’ll update the DOM strictly on a price change, which is much more optimal
than doing it naïvely everything 2 seconds.
457562 (3) [Avatar] Offline
#8
Page 169, second sentence after Listing 6.8: should be => operator

The subscriber receives the array and reduces it with a simple const add = (x,
y) -> => x + y;...
Rob A. (8) [Avatar] Offline
#9
Two errors on page 25.

First:
.then(promises => Promise.all(promise)) --> .then(promises => Promise.all(promiseS))


and in the next line there is a logic problem:
Second:
.then(promises => Promise.all(promise))
.then(
    dataInfo => ajax(`<host3>/data/files/${dataInfo.files}`)
)


here Promise.all returns a promise with an array that contains all resolved values. So in the following .then there is no .files property on the dataInfo object because that is an array. That means that there a .map() is missing which runs the ajax call on each item.
Rob A. (8) [Avatar] Offline
#10
Missing information about "swallowed errors" in promise context.
On page 188 the author says that promises can be swallowed if no error handler is provided. This is true for most browsers but at least newer versions of NodeJS throw an unhandled promise exception if an error is not handled.
Rob A. (8) [Avatar] Offline
#11
Page 116:
In the code where combineLatest is used there is a comma missing after the filter method (before the submit$).
Rx.Observable.combineLatest(
  password$
    .bufferTime(7000)
    .filter(R.compose(R.not, R.isEmpty))
  submit$
)


should be

Rx.Observable.combineLatest(
  password$
    .bufferTime(7000)
    .filter(R.compose(R.not, R.isEmpty)),
  submit$
)
Rob A. (8) [Avatar] Offline
#12
Page 223:
const p = new Promise((resolve, reject) => {
  setTimeout(() =>{
    let isAtAfter10pm = moment().hour() >= 20;
    if(isAtAfter10pm) {
      reject(new Error('Too late!'));
    }
    else {
      resolve('Success!');
    }
  }, 5000);
});


In the text it says "after 10 p.m." but moment().hour() >= 20 is after 8 p.m.
Rob A. (8) [Avatar] Offline
#13
Page 234:

Two consts that have the same name. The second const should have a different name like sub2.
const ticks$ = symbol$.mergeMap(fetchDataInterval$).share();

const sub1 = ticks$.subscribe(
  quoteDetails => updatePanel1(quoteDetails.symbol, quoteDetails.price)
);

const sub1 = ticks$.subscribe(
  quoteDetails => updatePanel2(quoteDetails.symbol, quoteDetails.price)
);
539022 (2) [Avatar] Offline
#14
page 167

example_url side effect, there is no use in input, since:

1. example_url is hardcoded
2. target.value is not propagated via pipeline in current implementation

https://github.com/RxJSInAction/rxjs-in-action/blob/master/examples/6/5/6_5.js

Thx
-Yura
Susan Harkins (401) [Avatar] Offline
#15
An errata list for Errata in RxJS in Action is available at https://manning-content.s3.amazonaws.com/download/1/efb5a96-45ae-481a-8e32-52a01142cac2/Atencio_RxJSinAction_Err1.html. Thanks!

Susan Harkins
Errata Editor
Gi11i4m (1) [Avatar] Offline
#16
116.jpg
[ 510 KB ]
p.116

The code says .take(10) but the comment says "Accepts only three password tries".

Either we don't understand the code or this is a wrong number.
|\|370 (2) [Avatar] Offline
#17
Gi11i4m wrote:p.116

The code says .take(10) but the comment says "Accepts only three password tries".

Either we don't understand the code or this is a wrong number.


Yes, I have the same feeling that it should say .take(3) instead of .take(10) in order to make sense.
|\|370 (2) [Avatar] Offline
#18
539022 wrote:page 167

example_url side effect, there is no use in input, since:

1. example_url is hardcoded
2. target.value is not propagated via pipeline in current implementation

https://github.com/RxJSInAction/rxjs-in-action/blob/master/examples/6/5/6_5.js

Thx
-Yura


Indeed!
tempusfugit (144) [Avatar] Offline
#19
Page 256(283) pdf:

The generator function has to be invoked (numbers()) rather than just passed (numbers - which will cause a TypeError) as an argument for from. So

  Rx.Observable.from(numbers)
      .take(10)
      .reduce(adder)
      .subscribe(total => {
          expect(total).to.equal(45);
      });

should be
  Rx.Observable.from(numbers())
      .take(10)
      .reduce(adder)
      .subscribe(total => {
          expect(total).to.equal(45);
      });


// tests/adding.spec.js
// page 255: Listing 9.5 Testing a stream that adds up all numbers of an array
//
import { from } from 'rxjs'
import { delay, reduce, take } from 'rxjs/operators'

describe('Adding numbers', () => {
  const adder = (total, delta) => total + delta
  const onNext = total => {
    expect(total).toBe(45)
  }

  it('Should add numbers together', () => {
    from([1, 2, 3, 4, 5, 6, 7, 8, 9]).pipe(
      reduce(adder)
    ).subscribe(onNext)
  })

  it('Should add numbers from a generator', () => {
    function* numbers() {
      let start = 0
      while (true) {
        yield start++
      }
    }

    from(numbers()).pipe(
      take(10),
      reduce(adder)
    ).subscribe(onNext)
  })

  // p. 257: Listing 9.6 Testing an observable with a delay
  //
  it('Should add number together with delay', (done) => {
    from([1, 2, 3, 4, 5, 6, 7, 8, 9]).pipe(
      reduce(adder),
      delay(1000)
    ).subscribe(onNext, null, done) // done() is called when complete
  })
})

tempusfugit (144) [Avatar] Offline
#20
Page 261(288) pdf:

The code (and explanations) in the 'Should schedule things in order' test would be appropriate for the Rx.Scheduler.asap scheduler:
If however you set delay to 0, asap will wait for current synchronously executing code to end and then it will try to execute given task as fast as possible.
For the Rx.Scheduler.queue on the other hand:
When used without delay, it schedules given task synchronously - executes it right when it is scheduled.
In fact trying to use Rx.Scheduler.queue.flush() will result in: TypeError: Cannot read property 'execute' of undefined.

// file: tests/queue.spec.js
// p. 261 - 9.5 Scheduling values in RxJS
//
import { asapScheduler, asyncScheduler, queueScheduler, range } from 'rxjs'
import { tap } from 'rxjs/operators'

describe('Rx.Scheduler.queue', () => {
  const expected = [1, 2, 3, 4, 5]

  function makeActionStore (state, into) {
    return () => into.push(state)
  }

  it('Schedule things synchronously', () => {
    let stored = []

    queueScheduler.schedule(makeActionStore(1, stored)) // "schedule" actions
    queueScheduler.schedule(makeActionStore(2, stored))
    queueScheduler.schedule(makeActionStore(3, stored))
    queueScheduler.schedule(makeActionStore(4, stored))
    queueScheduler.schedule(makeActionStore(5, stored))

    // queueScheduler.flush() // TypeError: Cannot read property 'execute' of undefined

    expect(stored).toEqual(expected)
  })

  it('Schedule things after current synchronous code', () => {
    let stored = []

    asapScheduler.schedule(makeActionStore(1, stored)) // 1. "schedule" actions
    asapScheduler.schedule(makeActionStore(2, stored))
    asapScheduler.schedule(makeActionStore(3, stored))
    asapScheduler.schedule(makeActionStore(4, stored))
    asapScheduler.schedule(makeActionStore(5, stored))

    expect(stored).toEqual([]) // 2. nothing has changed yet

    asapScheduler.flush() // 3. run actions

    expect(stored).toEqual(expected) // 4. actions have run
  })

  function makeAppendTo (arr) {
    return arr.push.bind(arr)
  }

  // p.262
  it('Emits values synchronously on default scheduler', () => {
    let temp = []
    const onNext = value => {
      expect(temp.length).toBe(value)
      expect(temp).toContain(value)
    }

    range(1, 5).pipe(
      tap(makeAppendTo(temp))
    ).subscribe(onNext)
  })

  // p.262 - Listing 9.8 values on an async scheduler
  it('Emits values synchronously on an asynchronous scheduler', (done) => {
    let temp = []
    const observer = {
      next (value) {
        expect(temp.length).toBe(value)
        expect(temp).toContain(value)
      },
      error (_error) { done() },
      complete () { done() }
    }

    range(1, 5, asyncScheduler).pipe(
      tap(makeAppendTo(temp))
    ).subscribe(observer)
  })
})
tempusfugit (144) [Avatar] Offline
#21
Page 266(293) pdf: Listing 9.10 Testing the debounceTime operator
let expected = '------a--------b------(s|)';
should be
let expected = '------a--------b------(c|)';
as there is no s in the source marbles but a c instead
let source = scheduler.createHotObservable(
    '-a--------b------c----|');


Page 267(294) pdf: Listing 9.11 Speeding up runInterval() with the virtual time scheduler
While
let expected = '-------------------(s-|';
seems to work
let expected = '-------------------(s|)';

was probably intended.
tempusfugit (144) [Avatar] Offline
#22
Not an erratum but a heads up for anyone approaching p.263: 9.6 Augmenting virtual reality with RxJS 6:

The user API for TestScheduler has changed:
  • User tests are executed with a callback passed to testScheduler.run(helpers => { ... }). The helpers object exposes the API to interact with the testing environment.
  • Within that callback TestScheduler.frameTimeFactor is 1 (1ms), not 10 (10ms) as it is on the outside for legacy reasons - i.e. 1 is the new default.
  • A time progression syntax has been introduced so that, for example, 5 empty frames can be expressed as '5ms' rather than '-----'.


  • Contrasting both ways:
    // p.265 Listing Testing the map() operator
    //
    function square (x) {
      return x * x
    }
    
    function makeTestScheduler (t) {
      const deepEqual = (actual, expected) => {
        t.deepEqual(actual, expected)
      }
      return new TestScheduler(deepEqual)
    }
    
    test('Map operator should map multiple values (rxjs 5 style)', t => {
      const sourceMarbles =
            '--1--2--3--4--5--6--7--8--9--|'
      const subs =
            '^----------------------------!'
      const expected =
            '--a--b--c--d--e--f--g--h--i--|' // marbles
      const values = {
        a: 1, b: 4, c: 9, d: 16, e: 25, f: 36, g: 49, h: 64, i: 81
      }
      let scheduler = makeTestScheduler(t)
      let source = scheduler.createColdObservable(sourceMarbles)
    
      let r = source.pipe(
        map(square)
      )
    
      scheduler.expectObservable(r).toBe(expected, values)
      scheduler.expectSubscriptions(source.subscriptions).toBe(subs)
    
      scheduler.flush()
    })
    
    test('Map operator should map multiple values (rxjs 6 style)', t => {
      const sourceMarbles =
            '--1--2--3--4--5--6--7--8--9--|'
      const subs =
            '^----------------------------!'
      const expected =
            '--a--b--c--d--e--f--g--h--i--|' // marbles
      const values = {
        a: 1, b: 4, c: 9, d: 16, e: 25, f: 36, g: 49, h: 64, i: 81
      }
      let scheduler = makeTestScheduler(t)
    
      const testMapMultiValues = helpers => {
        const { cold, expectObservable, expectSubscriptions } = helpers
        let source = cold(sourceMarbles)
        let r = source.pipe(
          map(square)
        )
    
        expectObservable(r).toBe(expected, values)
        expectSubscriptions(source.subscriptions).toBe(subs)
      }
    
      scheduler.run(testMapMultiValues) // i.e. no flush necessary
    })
    
    More code from 9.6 Augmenting virtual reality.
    // tests/marbles.js
    // p.263: 9.6 Augmenting virtual reality
    //
    import test from 'ava'
    import { runInterval } from '../src/runInterval'
    import { Notification } from 'rxjs'
    import { debounceTime, map } from 'rxjs/operators'
    import { TestScheduler } from 'rxjs/testing'
    
    test('Create time from a marble diagram', t => {
      let scheduler = new TestScheduler()
      let time = scheduler.createTime('-----|')
      t.is(time, 50)
    })
    
    // p.264. 9.6.1 Playing with marbles
    function makeTestMessage (frame, value) {
      const notification = value
        ? Notification.createNext(value)
        : Notification.createComplete()
      return { frame, notification }
    }
    
    test('Should parse a marble string into a series of notifications', t => {
      const expected = [
        makeTestMessage(20, 'A'),
        makeTestMessage(60, 'B'),
        makeTestMessage(100)
      ]
    
      let result = TestScheduler.parseMarbles(
        '--a---b---|',
        { a: 'A', b: 'B' }
      )
    
      t.deepEqual(result, expected)
    })
    
    // p.265 Listing Testing the map() operator
    //
    function square (x) {
      return x * x
    }
    
    function makeTestScheduler (t) {
      const deepEqual = (actual, expected) => {
        t.deepEqual(actual, expected)
      }
      return new TestScheduler(deepEqual)
    }
    
    test('Map operator should map multiple values (rxjs 5 style)', t => {
      const sourceMarbles =
            '--1--2--3--4--5--6--7--8--9--|'
      const subs =
            '^----------------------------!'
      const expected =
            '--a--b--c--d--e--f--g--h--i--|' // marbles
      const values = {
        a: 1, b: 4, c: 9, d: 16, e: 25, f: 36, g: 49, h: 64, i: 81
      }
      let scheduler = makeTestScheduler(t)
      let source = scheduler.createColdObservable(sourceMarbles)
    
      let r = source.pipe(
        map(square)
      )
    
      scheduler.expectObservable(r).toBe(expected, values)
      scheduler.expectSubscriptions(source.subscriptions).toBe(subs)
    
      scheduler.flush()
    })
    
    test('Map operator should map multiple values (rxjs 6 style)', t => {
      const sourceMarbles =
            '--1--2--3--4--5--6--7--8--9--|'
      const subs =
            '^----------------------------!'
      const expected =
            '--a--b--c--d--e--f--g--h--i--|' // marbles
      const values = {
        a: 1, b: 4, c: 9, d: 16, e: 25, f: 36, g: 49, h: 64, i: 81
      }
      let scheduler = makeTestScheduler(t)
    
      const testMapMultiValues = helpers => {
        const { cold, expectObservable, expectSubscriptions } = helpers
        let source = cold(sourceMarbles)
        let r = source.pipe(
          map(square)
        )
    
        expectObservable(r).toBe(expected, values)
        expectSubscriptions(source.subscriptions).toBe(subs)
      }
    
      scheduler.run(testMapMultiValues) // i.e. no flush necessary
    })
    
    // p.266: Listing 9.10 Testing the debouncTime operator
    //
    test('Should delay all events by the specified time', t => {
      const sourceMarbles =
        '10ms a 80ms b 60ms c 40ms |' // 194ms total (10 + 1 + 80 + 1 + 60 + 1 + 40 + 1)
      const subs =
        '^ 192ms !' // 194ms (1 + 192 + 1)
      const expected =
        '60ms a 80ms b 51ms (c|)' // 194ms (60 + 1 + 80 + 1 + 51 + 1)
      let scheduler = makeTestScheduler(t) // (value 'c' and complete at the same time)
    
      const testDebounce = helpers => {
        const { hot, expectObservable, expectSubscriptions } = helpers
        let source = hot(sourceMarbles)
        let r = source.pipe(
          debounceTime(50, scheduler)
        ) // frameTime factor is 1 NOT 10 for "run"
    
        expectObservable(r).toBe(expected)
        expectSubscriptions(source.subscriptions).toBe(subs)
      }
    
      scheduler.run(testDebounce)
    })
    
    test('Should delay all events by the specified time (alternate)', t => {
      const sourceMarbles =
            '1ms a 8ms b 6ms c 4ms |' // 23ms total (1 + 1 + 8 + 1 + 6 + 1 + 4 + 1)
      const subs =
            '^ 21ms !' // 23ms (1 + 21 + 1)
      const expected =
            '6ms a 8ms b 6ms (c|)' // 23ms (6 + 1 + 8 + 1 + 6 + 1)
      let scheduler = makeTestScheduler(t) // value 'c' and complete on the same frame
    
      const testDebounce = helpers => {
        const { hot, expectObservable, expectSubscriptions } = helpers
        let source = hot(sourceMarbles)
        let r = source.pipe(
          debounceTime(5, scheduler) // 5 instead of 50
        ) // frameTime factor is 1 NOT 10 for "run"
    
        expectObservable(r).toBe(expected)
        expectSubscriptions(source.subscriptions).toBe(subs)
      }
    
      scheduler.run(testDebounce)
    })
    
    // p.266: Listing 9.11 Speeding up runInterval() with the virtual time scheduler
    //
    test('Should square and add even numbers', t => {
      const sourceMarbles =
            '-1-2-3-4-5-6-7-8-9-|' // 20ms (20 frames)
      const subs =
            '^ 18ms !' // 20ms
      const expected =
            '19ms (s|)' // 20ms ('s' and complete are on the same frame)
      const values = { s: 120 }
      let scheduler = makeTestScheduler(t)
    
      const testRunInterval = helpers => {
        const { cold, expectObservable, expectSubscriptions } = helpers
        let source = cold(sourceMarbles)
        let r = runInterval(source)
    
        expectObservable(r).toBe(expected, values)
        expectSubscriptions(source.subscriptions).toBe(subs)
      }
    
      scheduler.run(testRunInterval)
    })
    
    // file: src/runInterval.js
    // p. 259 - 9.4 Making streams testable
    //
    import { filter, map, reduce, take } from 'rxjs/operators'
    
    function isEven (num) {
      return num % 2 === 0
    }
    
    function square (num) {
      return num * num
    }
    
    function add (a, b) {
      return a + b
    }
    
    export function runInterval (source) {
      return source.pipe(
        take(10),
        filter(isEven),
        map(square),
        reduce(add)
      )
    }
    
    // tests/search.js
    // p.269: Listing 9.12 Unit test main search logic
    //
    import test from 'ava'
    import { search } from '../src/search'
    import { of } from 'rxjs'
    import { TestScheduler } from 'rxjs/testing'
    
    const RX = 'rx'
    const RXJS = 'rxjs'
    const searchResults = new Map([
      [RX, [
        'rxmarbles.com',
        'https://www.manning.com/books/rxjs-in-action'
      ]],
      [RXJS, [
        'https://www.manning.com/books/rxjs-in-action'
      ]]
    ])
    
    function searchFn (term) {
      const key = term.toLowerCase()
      const result = searchResults.has(key) ? searchResults.get(key) : []
      return of(result)
    }
    
    function makeTestScheduler (t) {
      const deepEqual = (actual, expected) => {
        t.deepEqual(actual, expected)
      }
      return new TestScheduler(deepEqual)
    }
    
    test('Should test the search stream with debouncing', t => {
      // (ab) happens in the FIRST OF 4 FRAMES (each character is a frame)
      // 533ms = (10 + 4 + 517 + 1 + 1)
      const sourceMarbles = '10ms (ab) 517ms c|'
      const sourceValues = {
        a: 'r',
        b: RX,
        c: RXJS
      }
      // (s|) happens in the FIRST OF 4 FRAMES
      // BUT COMPLETES - so counts only as 1
      // 533ms = (510 + 1 + 21 + 1)
      const expected = '510ms f 21ms (s|)'
      const values = {
        f: searchResults.get(RX),
        s: searchResults.get(RXJS)
      }
      const subs = '^ 531ms !'
    
      let scheduler = makeTestScheduler(t)
    
      const testSearch = helpers => {
        // Note: frameTimeFactor is 1 (not 10)
        const { hot, expectObservable, expectSubscriptions } = helpers
        let source = hot(sourceMarbles, sourceValues)
        let r = search(source, searchFn, '', scheduler)
    
        expectObservable(r).toBe(expected, values)
        expectSubscriptions(source.subscriptions).toBe(subs)
      }
    
      scheduler.run(testSearch)
    })
    
    // file: src/notEmpty.js
    // p.248 - 9.1 Testing is inherently built into functional programs
    //
    export function notEmpty (input) {
      return !!input && input.trim().length > 0
    }
    
    // file: src/search.js
    // p.268 - 9.6.3 Refactoring your search stream for testability
    //
    import { notEmpty } from './notEmpty'
    import { debounceTime, filter, map, switchMap, tap } from 'rxjs/operators'
    
    function logSearchTerm (term) {
      console.info(`Searching with term ${term}`)
    }
    
    export function search (source, fetchResult, url = '', scheduler = null) {
      return source.pipe(
        debounceTime(500, scheduler),
        filter(notEmpty),
        tap(logSearchTerm),
        map(query => url + query),
        switchMap(fetchResult)
      )
    }