过滤 obj[array[@]] 为 nil 或空的数组

Filter an array where obj[array[@]] is nil or empty

再次练习 Ramda

所以情况是我有一个对象:

const originalObj = {
  foo: "bar",
  std: "min",
  baz: "",
  key1: undefined,
  key2: "exit",
  key3: "val3",
  key4: "",
};

我有一个我事先知道的数组:

const toCheckArray = ["baz", "key1", "key2", "key3", "key4", "key5"];

对于数组中的每一个元素,我都需要检查obj中是否存在该元素(如key),以及对应的值是否为nil/empty。如果存在这样的键,并且值为 non-zero/empty,那么我会像这样进行 HTTP 调用来更新值:

const findKey2AndUpdateObj = async (originalObj) => {
  const originalKey2 = originalObj.key2;
  const key2 = await remoteHttpCall(originalKey2);
  return { ...originalObj, key2: key2 };
};

对于数组中的所有元素,远程 HTTP 调用将完全相同,当然,除了负载之外。

我的做法是先过滤列表,执行以下步骤:

  1. const hasArray = filter(has(__, originalObj), toCheckArray); 我相信这将检查目标对象中是否存在作为 prop 的元素;
  2. 我正在尝试将 complement(anyPass([isNil, isEmpty])) 应用于 obj 的所有值,然后以某种方式过滤数组中的相应键;
  3. 迭代数组?进行 API 调用,然后更新对象。

我想我的想法并不是最好的方法。很想听听你的想法! 记住 API 调用也很棒!


或者我应该翻转第 1 步和第 2 步?从 obj 中过滤掉所有 nil/empty 个,然后进行 has 检查。


我最终这样做了: filter(has(__, reject(anyPass([isEmpty, isNil]))(obj)), __)(arr)。但肯定有更好的方法。

干杯!

使用管道,你可以将对象传递到管道并输出键,这样你就可以让它更实用一些,比如

pipe(reject(either(isNil, isEmpty)),keys,intersection(arr))(obj)

然后您可以将其通过管道传输到 api 调用中(使用 pipeWith

我会使用 R.pick 获取仅包含请求键的部分对象,然后使用 R.reject 进一步过滤它们。由于结果是一个对象,您可以使用 R.toPairs,并迭代这些对以进行 api 调用,并使用新值重建对象。

const { curry, pipe, pick, reject, anyPass, isEmpty, isNil } = R;

const fn = curry((arr, obj) => pipe(
  pick(arr), 
  reject(anyPass([isEmpty, isNil])),
)(obj));

const toCheckArray = ["baz", "key1", "key2", "key3", "key4", "key5"];
const originalObj = {"foo":"bar","std":"min","baz":"","key2":"exit","key3":"val3","key4":""};

const result = fn(toCheckArray, originalObj);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

函数式编程不一定是使用 Ramda, 有时(例如异步任务),它可能对可读性和声明性代码没有帮助。我会在这里使用简单的递归。

const asyncUpdater = async (keys, updater, data) => {
  // base case
  if(!keys.length) { return data };
  
  const [head, ...tail] = keys;
  const next = (d) => asyncUpdater(tail, updater, d);
  
  const value = data[head];
  
  // continue
  if(!value) { return next(data); }
  
  return next({ 
    ...data, 
    [head]: await updater(head, value)
  })
};

// ===
const keys = ["baz", "key1", "key2", "key3", "key4", "key5"];

const data = {
  foo: "bar",
  std: "min",
  baz: "",
  key1: undefined,
  key2: "exit",
  key3: "val3",
  key4: "",
};

function fakeHttp(key, value) {

  return Promise.resolve(`${value}__REMOTELY_UPDATED__`);
}

asyncUpdater(keys, fakeHttp, data).then(console.log);

希望我正确理解了您的要求。干净,自我记录,并且像香草 Javascript 允许的那样功能。

const resolveProperties = curry((fn, picklist, input) =>
  Promise.resolve(input)
    .then(pickAll(picklist))
    .then(reject(anyPass([isNil, isEmpty])))
    .then(map(fn))
    .then(Promise.props)
);

用法和测试用例:

import { pickAll, reject, isNil, map, anyPass, isEmpty, curry } from "ramda";
import { Promise } from "bluebird";

const mockHttp = (input) =>
  new Promise((res) => setTimeout(res(`${input} resolved`), 100));

const resolveProperties = curry((fn, picklist, input) =>
  Promise.resolve(input)
    .then(pickAll(picklist))
    .then(reject(anyPass([isNil, isEmpty])))
    .then(map(fn))
    .then(Promise.props)
);

test("resolveProperties", () => {
  const list = ["baz", "key1", "key2", "key3", "key4", "key5"];

  const input = {
    foo: "bar",
    std: "min",
    baz: "",
    key1: undefined,
    key2: "exit",
    key3: "val3",
    key4: "",
  };

  return resolveProperties(mockHttp, list, input).then((result) =>
    expect(result).toEqual({
      key2: "exit resolved",
      key3: "val3 resolved",
    })
  );
});