4
\$\begingroup\$

I'm using RxJS 5.0.0-alpha.12 to perform some data transformations, and some of those transformations use Promises.

return observable
.flatMap(item => {
  return Observable.zip(

    Observable.fromPromise(fs.readFile('./config.json')),

    Observable.of(item),

    (s1, s2) => {
      return {
        config: JSON.parse(s1.toString()),
        testResult: s2
      };
    }
  );
})
.flatMap(item => {
  return Observable.zip(

    Observable.fromPromise(
      getStudentIdFromStudentNumber(item.testResult['Student Primary ID'])
    ),

    Observable.of(item),

    (s1, s2) => {
      return {
        'Test Date': s2.config.testConstants.ROGL_Begin_Year.testDate,
        'Student Id': s1,
        'Student Number': s2.testResult['Student Primary ID'],
        'Grade Level': s2.testResult.Grade,
        'Composite Score Alpha': s2.testResult['Composite Score']
      };
    }
  );

});

Note that both fs.readFile (fs here being the name given to fs-promise) and getStudentIdFromStudentNumber return a Promise.

It seems like having to zip the original item that is getting emitted from observable with the results of each Promise is cumbersome. Is there a cleaner way to use the results of these two Promises with this Observable?

What makes this tricky (to me) is sometimes the Promise relies on a field within the item object emitted by observable (eg., getStudentIdFromStudentNumber), and other Promises may not rely on item (eg., fs.readFile).

\$\endgroup\$
1
+50
\$\begingroup\$

You are (twice) using Observable.of(item), this is redundant as item is already in scope. Also, you can call both promises in parallel. zip is perfect for exactly that.

If you want to make transformations to an item before you use it, put a map inside the zip. For example, we can extract the testDate early.

While we're on it, we can use meaningfull variable names.

If you have more than 2 promises, zip will happily accept 3, 4 or more arguments.

Lastly we use flatMap around the zip to flatten the stream of zip observables back to items.

return observable
  .flatMap(item => {
    return Observable.zip(
      Observable.fromPromise(fs.readFile('./config.json'))
      .map(config => JSON.parse(config.toString().testConstants.ROGL_Begin_Year.testDate))
      , Observable.fromPromise(
        getStudentIdFromStudentNumber(item.testResult['Student Primary ID'])
      ), (testDate, id) => {
        return {
          'Test Date': testDate,
          'Student Id': id,
          'Student Number': item['Student Primary ID'],
          'Grade Level': item.Grade,
          'Composite Score Alpha': item['Composite Score']
        };
      }
    )
  });
\$\endgroup\$
  • \$\begingroup\$ Your answer is good, but I think it's missing a return before Observable.zip \$\endgroup\$ – Nathan Jones Jan 14 '16 at 0:00
  • \$\begingroup\$ AH right, i usually use the (param1, param2, …, paramN) => expression syntax, used return for clarity here, and then forgot one :) \$\endgroup\$ – Dorus Jan 14 '16 at 0:09
1
\$\begingroup\$

I would be more inclined to map with the flatMap to have my results flow through.

Something like the following, might do what you're looking for.

return observable
  .flatMap(item => Observable.fromPromise(fs.readFile('./config.json'))
    .map(file => ({ config: JSON.parse(file.toString()), testResult: item })))
  .flatMap(({ config, testResult }) => Observable.fromPromise(getStudentIdFromStudentNumber(item.testResult['Student Primary ID']))
    .map(id => ({
      'Test Date': config.testConstants.ROGL_Begin_Year.testDate,
      'Student Id': id,
      'Student Number': testResult['Student Primary ID'],
      'Grade Level': testResult.Grade,
      'Composite Score Alpha': testResult['Composite Score']
    })));
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! Could you elaborate a little on why a flatMap would be more advantageous to the OP? \$\endgroup\$ – SuperBiasedMan Jan 13 '16 at 16:27
1
\$\begingroup\$

One option is to use the scope of the flatMap to your advantage. Since you have to create an Observable from the promises, you can just return that directly. However, you still want to use the item in each flatMap, so you can map the result of the Observable you created. I think code is clearer than words in this situation.

return observable
.flatMap(item => {
  return Observable.fromPromise(fs.readFile('./config.json'))
  .map(s1 => {
    return {
      config: JSON.parse(s1.toString()),
      testResult: item
    };
  });
})
.flatMap(item => {
  return Observable.fromPromise(
    getStudentIdFromStudentNumber(item.testResult['Student Primary ID'])
  )
  .map(s1 => {
    return {
      'Test Date': item.config.testConstants.ROGL_Begin_Year.testDate,
      'Student Id': s1,
      'Student Number': item.testResult['Student Primary ID'],
      'Grade Level': item.testResult.Grade,
      'Composite Score Alpha': item.testResult['Composite Score']
    };
  });
});
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.