如何结合使用Promise和静态回调来使用Ramda Pipe函数?

时间:2019-10-22 12:34:39

标签: javascript bluebird ramda.js

基于help of @ScottSauyet,我已经能够创建一个函数,用于为初始数据对象解决静态和基于承诺的回调。

现在,我希望能够通过一系列回调通过管道传递此数据对象,但是一旦将多个promise添加到组合中,就会遇到麻烦。

当前设置

@RunWith(RobolectricTestRunner.class)
public class MyContentProviderTest {

    private ContentResolver contentResolver;
    private MyRepository repository;

    @Before
    public void setup() {
        repository = mock(MyRepository.class);
        contentResolver = ApplicationProvider.getApplicationContext().getContentResolver();

        ProviderInfo info = new ProviderInfo();
        info.authority = "my.authority";
        info.grantUriPermissions = true;
        ContentProviderController<MediaContentProvider> controller = Robolectric
                .buildContentProvider(MyContentProvider.class)
                .create(info);

        MyContentProvider provider = controller.get();
        // Method in my ContentProvider that allows to override
        // the repository it uses
        provider.setRepository(repository);
    }

    @Test
    public void testQuery() {

        // setup expectations in mockito
        when(repository.getAll()).thenReturn(...);

        try (Cursor cursor = contentResolver.query(SOME_URI, null, null, null, null)) {
            // test cursor contents here
        }

    }
}

管道尝试

// Libaries
const R = require('ramda');
const fetch = require('node-fetch');
const Promise = require('bluebird');

// Input
const data = {
  array: [['#', 'FirstName', 'LastName'], ['1', 'tim', 'foo'], ['2', 'kim', 'bar']],
  header: 'FirstName',
  more: 'stuff',
  goes: 'here'
};

// Static and Promise Resolver (with Helper Function)
const transposeObj = (obj, len = Object.values(obj)[0].length) =>
  [...Array(len)].map((_, i) => Object.entries(obj).reduce((a, [k, v]) => ({ ...a, [k]: v[i] }), {}));

const mergeCallback = async ({ array: [headers, ...rows], header, ...rest }, callback) => {
  const index = R.indexOf(header, headers);
  const result = await Promise.map(rows, row => {
    return callback(row[index]);
  })
    .then(x => ({ changes: x.map(v => transposeObj(v.changes)) }))
    .then(({ changes }) => ({
      allHeaders: R.flatten([
        ...headers,
        R.chain(t => R.chain(Object.keys, t), [...changes])
          .filter(k => !headers.includes(k))
          .filter((x, i, a) => a.indexOf(x) == i)
      ]),
      changes
    }))
    .then(({ changes, allHeaders }) => ({
      resultRows: R.chain(
        (row, i = R.indexOf(row, [...rows])) =>
          changes[i].map(change =>
            Object.entries(change).reduce(
              (r, [k, v]) => [...r.slice(0, allHeaders.indexOf(k)), v, ...r.slice(allHeaders.indexOf(k) + 1)],
              row.slice(0)
            )
          ),
        [...rows]
      ),
      allHeaders
    }))
    .then(({ resultRows, allHeaders, array }) => ({
      array: [allHeaders, ...resultRows],
      header,
      ...rest
    }));
  return result;
};

// Example Callbacks and their services
const adapterPromise1 = async name => {
  const response = await fetch(`https://api.abalin.net/get/getdate?name=${name}&calendar=us`).then(res => res.json());
  return {
    changes: {
      nameday: R.pluck('day', response.results),
      namemonth: R.pluck('month', response.results)
    }
  };
};
const servicePromise1 = input => mergeCallback(input, adapterPromise1);

const adapterPromise2 = async name => {
  const response = await fetch(`https://api.genderize.io?name=${name}`).then(res => res.json());
  return {
    changes: {
      gender: R.of(response.gender)
    }
  };
};
const servicePromise2 = input => mergeCallback(input, adapterPromise2);

const adapterStatic1 = name => ({ changes: { NameLength: R.of(R.length(name)) } });
const serviceStatic1 = input => mergeCallback(input, adapterStatic1);

预期结果

const result = R.pipe(
  servicePromise1,
  servicePromise2,
  serviceStatic1
)(data);

// console.log(result); <<< preferred resolution method, but not working due to promise
result.then(console.log);

当前结果

管道可用于任何一个服务调用,但是一旦我尝试使用两个或多个服务,就会收到以下错误消息。

{ array:  
   [ [ '#', 
       'FirstName', 
       'LastName', 
       'nameday', 
       'namemonth', 
       'gender', 
       'NameLength' ], 
     [ '1', 'tim', 'foo', 24, 1, 'male', 3 ], 
     [ '1', 'tim', 'foo', 20, 6, 'male', 3 ], 
     [ '2', 'kim', 'bar', 8, 9, 'male', 3 ], 
     [ '2', 'kim', 'bar', 11, 10, 'male', 3 ] ], 
  header: 'FirstName', 
  more: 'stuff', 
  goes: 'here' } 

任何有关如何使其正常工作的提示将不胜感激。

1 个答案:

答案 0 :(得分:2)

Ramda的pipe不支持Promise。已弃用支持Promise的旧版本pipeP,而推荐使用更通用的pipeWith。您可以通过传递R.then(很快将其重命名为R.andThen)与Promises一起使用,如下所示:

R.pipeWith (R.then, [
//servicePromise1, // problem with CORS headers here.
  servicePromise2,
  serviceStatic1
]) (data)
.then (console .log)

出于某种原因,当我尝试从Ramda的REPL或SO代码段运行它时,您的第一个API调用对我来说会遇到CORS问题,但是没有它,过程应该很清楚。

这可能足以解决您的问题。它适用于此测试用例。但是我看到一个突出的问题:pipe的所有版本都会将上一个调用的结果传递到下一个调用。但是,您可以使用数据的属性来配置有关如何触发下一个回调的信息,即header属性。因此,必须在整个管道中保持固定。如果所有调用都将使用FirstName属性,那很好,但是我的印象是,他们需要自己的版本。

但是编写一个自定义管道函数将使您将其与回调函数一起传递将很容易。然后您的呼叫可能看起来像这样:

seq ([
  ['FirstName', servicePromise2],
  ['FirstName', serviceStatic1]
]) (data)
.then(console.log)

您可以在以下代码段中看到该想法的有效版本:

// Input
const data = {
  array: [['#', 'FirstName', 'LastName'], ['1', 'tim', 'foo'], ['2', 'kim', 'bar']],
  header: 'FirstName',
  more: 'stuff',
  goes: 'here'
};

// Static and Promise Resolver (with Helper Function)
const transposeObj = (obj, len = Object.values(obj)[0].length) =>
  [...Array(len)].map((_, i) => Object.entries(obj).reduce((a, [k, v]) => ({ ...a, [k]: v[i] }), {}));

const mergeCallback = async ({ array: [headers, ...rows], header, ...rest }, callback) => {
  const index = R.indexOf(header, headers);
  const result = await Promise.all(rows.map(row => {
    return callback(row[index]);
  }))
    .then(x => ({ changes: x.map(v => transposeObj(v.changes)) }))
    .then(({ changes }) => ({
      allHeaders: R.flatten([
        ...headers,
        R.chain(t => R.chain(Object.keys, t), [...changes])
          .filter(k => !headers.includes(k))
          .filter((x, i, a) => a.indexOf(x) == i)
      ]),
      changes
    }))
    .then(({ changes, allHeaders }) => ({
      resultRows: R.chain(
        (row, i = R.indexOf(row, [...rows])) =>
          changes[i].map(change =>
            Object.entries(change).reduce(
              (r, [k, v]) => [...r.slice(0, allHeaders.indexOf(k)), v, ...r.slice(allHeaders.indexOf(k) + 1)],
              row.slice(0)
            )
          ),
        [...rows]
      ),
      allHeaders
    }))
    .then(({ resultRows, allHeaders, array }) => ({
      array: [allHeaders, ...resultRows],
      header,
      ...rest
    }));
  return result;
};

// Example Callbacks and their services
const adapterPromise2 = async (name) => {
  const response = await fetch(`https://api.genderize.io?name=${name}`).then(res => res.json());
  return {
    changes: {
      gender: R.of(response.gender)
    }
  };
};
const servicePromise2 = input => mergeCallback(input, adapterPromise2);

const adapterStatic1 = name => ({ changes: { NameLength: R.of(R.length(name)) } });
const serviceStatic1 = input => mergeCallback(input, adapterStatic1);

const seq = (configs) => (data) =>
  configs.reduce(
    (pr, [header, callback]) => pr.then(data => callback({...data, header})),
    Promise.resolve(data)
  )

seq ([
  ['FirstName',  servicePromise2],
  ['FirstName', serviceStatic1]
]) (data)
.then(console.log)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

尽管如此,我仍然认为这有些尴尬。您正在寻找的标题名称根本不属于该输入数据。您可以将其设为mergeCallback函数的另一个属性,并更新包装程序以从那里传递它,例如

const servicePromise2 = (input) => mergeCallback(input, 'FirstName', adapterPromise2);

在我看来,即使我理解这会为您现有的回调函数增加一些工作,但还是将整个行传递给回调函数,该回调函数构造为具有所有标头作为属性的对象。 Ramda的zipObj可以这样使用:

  const result = await Promise.all(rows.map(row => {
    return callback(zipObj(headers, row));
  }))

传递给每个回调对象,如下所示:

{"#":"1", FirstName: "tim", LastName: "foo" /*, gender: 'male', ... */}

您可以将回调的签名更改为

const adapterPromise2 = async ({FirstName: name}) => { ...use `name` ... }

保留主体不变,或简单地将变量名更改为FirstName以匹配对象。

const adapterPromise2 = async ({FirstName}) => { ...use `FirstName`... }

无论哪种方式,这都将使通用代码更简单,在不显着更改现有回调的情况下,删除在当前API中感觉很尴尬的header属性。