javascript - 如何将 Ramda Pipe 函数与 promise 和静态回调混合使用?

标签 javascript bluebird ramda.js

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

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

当前设置

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

当前结果

Pipe 适用于任何一个服务调用,但一旦我尝试使用两个或更多服务,我就会收到以下错误。

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

任何关于如何让它工作的提示都将不胜感激。

最佳答案

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

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);

在我看来更好的是,即使我知道它会为您现有的回调函数添加一些工作,也就是将整行传递给回调函数,将其结构化为一个对象,并将所有标题作为属性。 lambda 的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 属性,而不会显着改变您现有的回调。

关于javascript - 如何将 Ramda Pipe 函数与 promise 和静态回调混合使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58504438/

相关文章:

javascript - 显示共享文件 google Drive api

javascript - 不在网络浏览器中显示 javascript 图形

javascript - 如何使用 Promises 链接和共享先前的结果

javascript - 如何使函数可以使用多个参数调用,因此您可以执行 f(x,y) 而不是 f(x)(y)

javascript - 将两个参数传递给 .map 回调函数

javascript - 拖动以像在 iPhone 中一样使用 jQuery 更改图像幻灯片

javascript - 为什么我的递归函数只适用于一级?

node.js - 包装异步方法以返回 promise (bluebird)

javascript - Node promise 链 : Local functions and how to pass multiple values (code-review)

javascript - 如何编写验证函数