如何结合使用Promise和静态回调来使用Ramda Pipe函数? [英] How to use Ramda Pipe function with a mix of promises and static callbacks?

查看:82
本文介绍了如何结合使用Promise和静态回调来使用Ramda Pipe函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于

Based on the help of @ScottSauyet I have been able to create a function resolving static and promise based callbacks for an initial data object.

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

Now I want to be able to pipe this data object through a series of callbacks, but run into trouble once I add multiple promises into the mix.

// 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' } 

当前结果

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

Current result

Pipe works with any one service call, but as soon as I try to use two or more services, I receive the following error.

Cannot read property 'Symbol(Symbol.iterator)' of undefined 

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

Any hint on how to get this to work would be greatly appreciated.

推荐答案

Ramda的pipe不支持Promise.已弃用Promise的旧版本pipeP,而推荐使用更通用的 pipeWith .您可以通过传递 R.then (很快将其重命名为R.andThen)将其与Promises结合使用.像这样:

Ramda's pipe is not Promise-aware. The old Promise-aware version, pipeP is being deprecated in favor of the more generic pipeWith. You can use it with Promises by passing R.then (soon to be renamed to R.andThen) like this:

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

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

For some reason your first API call is running into CORS issues for me when I try to run it from Ramda's REPL or a SO snippet, but the process should be clear without it.

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

This might be enough to fix your problem. It works for this test-case. But I see an outstanding issue: All versions of pipe pass through the result of the previous call to the next one. However you use a property of the data to configure something about how the next callback will be triggered, namely your header property. So that would have to stay fixed throughout your pipeline. It's fine if all calls are going to use the FirstName property, but my impression is that they need their own versions of it.

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

But it would be easy enough to write a custom pipeline function that let you pass this alongside the callback function. Then your call might look like this:

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

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

You can see a working version of that idea in this snippet:

// 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函数的另一个属性,并更新包装程序以从那里传递它,例如

I still think there is something awkward about this, though. The header name you're looking for to me does not belong in that input data at all. You could make it another property of your mergeCallback function and update your wrappers to pass it from there, like

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

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

Even better to my mind, even though I understand it would add some work to your existing callback functions, would be to pass the whole row to the callback function, structured as an object with all the headers as properties. Ramda's zipObj could be used like this:

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

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

to pass to each callback objects like this:

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

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

You could change the signature of the callback to look like

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

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

and leave the body intact, or simply change the variable name to FirstName to match the object.

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

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

Either way, this would leave the generic code simpler, remove the header property that feels quite awkward in your current API without significantly changing your existing callbacks.

这篇关于如何结合使用Promise和静态回调来使用Ramda Pipe函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆