Как последовательно запрашивать данные в Cycle.js?

Я новичок в реактивном программировании и играю с cycle.js, пытаясь реализовать поле «Кто следовать» из этого руководства. Но я понял, что для правильной реализации (и целей обучения) у меня нет одной части данных: полного имени пользователя. Я могу получить его, последовательно получая пользователей, а затем полные пользовательские данные с сервера. В императивном стиле я бы сделал что-то вроде этого:

fetch(`https://api.github.com/users`)
  .then(data => data.json())
  .then(users => fetch(users[0].url))
  .then(data => data.json())
  .then(/* ... work with data ... */)

Но как мне сделать это в цикле? Я использую драйвер извлечения и пытаюсь сделать что-то вроде этого:

function main({ DOM, HTTP }) {
  const users = `https://api.github.com/users`;

  const refresh$ = DOM.select(`.refresh`).events(`click`)

  const response$ = getJSON({ key: `users` }, HTTP)

  const userUrl$ = response$
    .map(users => ({
      url: R.prop(`url`, R.head(users)),
      key: `user`,
    }))
    .startWith(null)

  const request$ = refresh$
    .startWith(`initial`)
    .map(_ => ({
      url: `${users}?since=${random(500)}`,
      key: `users`,
    }))
    .merge(userUrl$)

  const dom$ = ...

  return {
    DOM: dom$,
    HTTP: request$,
  };
}

где getJSON

function getJSON(by, requests$) {
  const type = capitalize(firstKey(by));

  return requests$
    [`by${type}`](firstVal(by))
    .mergeAll()
    .flatMap(res => res.json());

И я всегда получаю какую-то загадочную (для меня) ошибку, например: TypeError: Already read. Что это значит и как правильно с этим обращаться?


person alice kibin    schedule 19.11.2015    source источник


Ответы (2)


Вы были совсем рядом. Вам просто нужно удалить startWith(null) как запрос и получить второй ответ (вам не хватало getJSON для этого).

function main({ DOM, HTTP }) {
  const usersAPIPath = `https://api.github.com/users`;

  const refresh$ = DOM.select(`.refresh`).events(`click`);

  const userResponse$ = getJSON({ key: `user` }, HTTP);
  const listResponse$ = getJSON({ key: `users` }, HTTP);

  const userRequest$ = listResponse$
    .map(users => ({
      url: R.prop(`url`, R.head(users)),
      key: `user`,
    }));

  const listRequest$ = refresh$
    .startWith(`initial`)
    .map(_ => ({
      url: `${usersAPIPath}?since=${Math.round(Math.random()*500)}`,
      key: `users`,
    }));

  const dom$ = userResponse$.map(res => h('div', JSON.stringify(res)));

  return {
    DOM: dom$,
    HTTP: listRequest$.merge(userRequest$),
  };
}
person André Staltz    schedule 22.11.2015
comment
Спасибо, это работает! Но мне все равно кажется, что это какая-то магия. Правильно ли я понимаю, что userRequest$ просто ждет, пока listResponse$ что-нибудь испустит, а затем просто делает то, что следует дальше по цепочке? - person alice kibin; 23.11.2015
comment
Правильный. Он сопоставляет событие от listResponse$ с новым запросом. - person André Staltz; 23.11.2015
comment
Понял. Еще раз спасибо, мне очень нравится Cycle.js, и я продолжу изучать его. - person alice kibin; 23.11.2015
comment
Наконец-то я понял, что HTTP-драйвер цикла js, используемый здесь, — это драйвер @cycle/fetch: github.com/ Cyclejs/цикл-fetch-драйвер - person bloodyKnuckles; 16.05.2016

Потому что пытливые умы хотят знать... вот полный рабочий пример:

import Cycle from '@cycle/rx-run';
import {div, button, makeDOMDriver} from '@cycle/dom';
import {makeFetchDriver} from '@cycle/fetch';
import R from 'ramda'

function main({DOM, HTTP}) {

  const usersAPIPath = 'https://api.github.com/users';
  const refresh$ = DOM.select('button').events('click');

  const userResponse$ = getJSON({ key: 'user' }, HTTP);
  const listResponse$ = getJSON({ key: 'users' }, HTTP);

  const userRequest$ = listResponse$
    .map(users => ({
      url: R.prop('url', R.head(users)),
      key: 'user',
    }));

  const listRequest$ = refresh$
    .startWith('initial')
    .map(_ => ({
      url: `${usersAPIPath}?since=${Math.round(Math.random()*500)}`,
      key: 'users',
    }));

  const dom$ = userResponse$.map(res => div([
    button('Refresh'),
    div(JSON.stringify(res))
  ]));

  return {
    DOM: dom$,
    HTTP: listRequest$.merge(userRequest$)
  };

  function getJSON(by, requests$) {
    return requests$.byKey(by.key)
      .mergeAll()
      .flatMap(res => res.json());
  }
}

Cycle.run(main, {
  DOM: makeDOMDriver('#main-container'),
  HTTP: makeFetchDriver()
});

Мне потребовалось некоторое время, чтобы понять, что HTTP был драйвером @cycle/fetch, а НЕ @cycle/http драйвер. Затем небольшой поиск обнаружил библиотеку ramda npm, предоставляющую методы prop и head.

person bloodyKnuckles    schedule 15.05.2016