【问题标题】:How does this resolver extension function work?这个解析器扩展功能是如何工作的?
【发布时间】:2020-07-12 01:19:12
【问题描述】:

我需要帮助来解释一段代码。

我一直在尝试学习如何使用 jwt 对用户进行身份验证并返回令牌和刷新令牌。

这是一次有趣的旅程,我遇到了这个repository,其中用户添加了一个扩展/链功能,据我所知,它结合了多个 graphql 解析器:

// I don't understand this function
const createResolver = (resolver) => {
  const baseResolver = resolver;
  baseResolver.createResolver = (childResolver) => {
    const newResolver = async (parent, args, context, info) => {
      await resolver(parent, args, context, info);
      return childResolver(parent, args, context, info);
    };
    return createResolver(newResolver);
  };
  return baseResolver;
};

export const requiresAuth = createResolver((parent, args, context) => {
  if (!context.user || !context.user.id) {
    throw new Error('Not authenticated');
  }
});

export const requiresAdmin = requiresAuth.createResolver((parent, args, context) => {
  if (!context.user.isAdmin) {
    throw new Error('Requires admin access');
  }
});

它是这样使用的:

Query: { 
    getBook: requiresAuth.createResolver((parent, args, { models }, info) =>
       // do something fun
    ),
}

我不理解 createResolver 函数,我想问这是否是某种类型的编程范式,其中有一些文章可以让我更好地阅读和理解它。

谷歌搜索我确实找到了graphql-resolvers,据我所知,它做同样的事情,但在这种情况下,我需要安装另一个我想避免但同时我不想要的包使用我不完全理解的功能。

编辑:

我对函数的理解/认为我理解:

通过调用一个函数,然后在该函数内部传入一个新函数,将多个函数链接在一起:requiresAuth.createResolver 在函数本身内部,我无法弄清楚。 我们得到一个新变量baseResolver,我们将它链接到一个新的createResolver 然后我们递归地调用父函数。

我猜我无法理解这个流程。

【问题讨论】:

  • 好吧,我对graphQL不熟悉,但代码只是将function添加到另一个function
  • @appleapple 我以前从未见过这样的语法。这个有名字吗?
  • @CodingLittle 我不确定是否有名称,但基本上你可以像 javascript 中的普通对象一样向函数添加任何内容(例如 {function X(){};X.a=1;console.log(X.a);}
  • {function X(){};X.a=()=>1;console.log(X.a());}
  • cmets 部分变得有点大。对于理解它的人,我能否得到一个答案,它被分解或以更简单的方式编写?我目前正在阅读高阶函数链接,但它仍然没有让我完全清楚地了解函数正在做什么。

标签: javascript graphql apollo-server


【解决方案1】:

我冒昧地重命名了一些东西,因为现有的名称非常混乱。

const augment = (resolver) => {
  resolver.add = (nextResolver) => {
    const wrapper = async (parent, args, context, info) => {
      await resolver(parent, args, context, info)
      return nextResolver(parent, args, context, info)
    }
    return augment(wrapper)
  }
  return resolver
}

在每一步都将.add 方法添加到返回的函数中,以便像这样启用链接:

augment(resolver1).add(resolver2).add(resolver3)...

每次调用 .add 时,都会创建一个新的异步 lambda 函数 (wrapper)。 wrapper 关闭 resolvernextResolver。最终运行时,wrapper会调用resolver,等待结果,然后调用nextResolver,返回结果。

wrapper 然后传递给augment 以向其添加.add 函数(以启用链接)。这个新的add 函数关闭了提供给augment 的参数(即wrapper)。返回增强的wrapper 函数。

因此,当随后调用.add 时,resolver 参数是在前一个解析器上调用.addwrapper 函数。所以:

await resolver(parent, args, context, info)
return nextResolver(parent, args, context, info)

...将等待之前创建的wrapper 函数,然后调用最新的nextResolver

这样,对.add 的连续调用会构造一个异步函数链。

当你想运行链时,你可以简单地调用返回的函数,忽略它上面的.add属性。

请注意,每个解析器都传递了相同的参数,以避免用户担心将它们传递到链下。

函数可以改写为:

const augment = (resolver) => {
  resolver.add = (nextResolver) => 
    augment((...args) => 
      resolver(...args)
        .then(() => nextResolver(...args)))
  return resolver
}

...或:

const inSequence = (resolvers) => 
    (...args) => 
        resolvers.reduce((acc, el) => 
            acc.then(() => el(...args)), Promise.resolve())

const getBook = inSequence([auth, admin, getBookResolver])
const query = { getBook }

...或:

const inSequence = (resolvers) => 
    async (...args) => {
        for(let el of resolvers) {
            await el(...args)
        }
    }
const getBook = inSequence([auth, admin, getBookResolver])
const query = { getBook }

【讨论】:

  • 别误会,但我现在可以拥抱你。这使它更容易理解,谢谢!
  • 我希望我没有要求太多,但您是否有一篇文章或某种文档,以便我可以阅读更多相关信息。再次感谢!
  • 本网站扩展了一些可能对您有所帮助的函数式编程概念:dev.to/damxipo/functional-programming-in-javascript-442p
【解决方案2】:
const createResolver = (resolver) => {
  const baseResolver = resolver;
  baseResolver.createResolver = (childResolver) => {
    const newResolver = async (parent, args, context, info) => {
      await resolver(parent, args, context, info);
      return childResolver(parent, args, context, info);
    };
    return createResolver(newResolver);
  };
  return baseResolver;
};

export const requiresAuth = createResolver( /*resolver_A*/ (parent, args, context) => {
  if (!context.user || !context.user.id) {
    throw new Error('Not authenticated');
  }
});

createResolver 返回的requiresAuth 是一个函数(作为参数传递,用resolver_A 标记)内部装饰有附加函数... 由.createResolver() 提供

export const requiresAdmin = requiresAuth.createResolver( /*resolver_B*/ (parent, args, context) => {
  if (!context.user.isAdmin) {
    throw new Error('Requires admin access');
  }
});

requiresAuth.createResolver( 调用另一个作为参数传递的解析器(resolver_BchildResolver)创建并返回新的异步解析器 newResolvernewResolver 调用 awaits 以获取 resolver(在本例中为 resolver_A)的结果,然后调用当前传递的解析器(resolver_B)。

createResolver(newResolver); 不仅返回链式异步解析器 - 再次使用 .createResolver 装饰函数('reccurency'),允许链式解析器。你可以再次使用.createResolver()

Query: { 
  editBook: requiresAdmin.createResolver((parent, args, { models }, info) =>

editBook 将在运行目标解析器之前依次检查身份验证 (if (!context.user || !context.user.id) ...) 和权限 (if (!context.user.isAdmin) ...)。

等于

editBook: createResolver(authResolverFn).createResolver(adminResolverFn).createResolver(editBookResolverFn)

...但只有第一个 createResolver 是“外部”函数的名称。

【讨论】:

  • 我认为更改 @52d6c6af 这样的名称确实让我更容易理解。无论如何,你的解释仍然让我的生活更轻松。还要感谢您在我的另一个问题中建议 jwt 令牌,它引导我这样做。
猜你喜欢
  • 2017-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 2013-10-28
  • 2017-07-29
相关资源
最近更新 更多