资讯专栏INFORMATION COLUMN

总结前端走 gRPC 协议所遇到的坑

Eminjannn / 751人阅读

摘要:坑点一不支持浏览器环境首先你应该了解,它的目标是将编译文件所生成的文件夹包含和文件。坑点二所提供的方法只支持回调函数以上是官方给出的例子,发送一个标准请求。

坑点一:ts-protoc-gen 不支持浏览器环境

首先你应该了解 ts-protoc-gen,它的目标是将编译 .proto 文件所生成的文件夹包含 .js.d.ts 文件。

但是...


请不要将 ts-protoc-gen 生成的代码直接用在浏览器中

因为当我们直接使用如下代码时:

import { MyMessage } from "../generated/users_pb";

const msg = new MyMessage();
msg.setName("John Doe");

报错:

Uncaught ReferenceError: exports is not defined.

这个错误应该不陌生,exports 未定义,多见于浏览器环境直接使用 node 环境代码,所以我翻看了一下 ts-protoc-gen 的源码,直到发现了如下代码:

printer.printLn(`exports.${service.name} = ${service.name};`);

这里的 exports 就已经说明一切了,这个库生成的是运行在 node 环境的 CommonJS 规范代码,而对于使用 Webpack4vue project 项目,也并不支持混合使用模块系统,所以目前我想到了个临时解决方案:

// 编译文件导出方法和类时强制使用 `es module`
// src/service/grpcweb.ts
printer.printLn(`var ${service.name} = (function () {`); // line 251
//  ||
//  ||
//  /
printer.printLn(`export var ${service.name} = (function () {`);



printer.printLn(`exports.${service.name} = ${service.name};`); // line 270
//  ||
//  ||
//  /
// delete



.printLn(`function ${service.name}Client(serviceHost, options) {`) // line 286
//  ||
//  ||
//  /
.printLn(`export function ${service.name}Client(serviceHost, options) {`)



printer.printLn(`exports.${service.name}Client = ${service.name}Client;`); // line 304
//  ||
//  ||
//  /
// delete

这个方法可以暂时解决 Uncaught ReferenceError: exports is not defined. 的问题。

坑点二:grpc-web-client 所提供的方法只支持回调函数
import {grpc} from "grpc-web-client";

// Import code-generated data structures.
import {BookService} from "./generated/proto/examplecom/library/book_service_pb_service";
import {GetBookRequest} from "./generated/proto/examplecom/library/book_service_pb";

const getBookRequest = new GetBookRequest();
getBookRequest.setIsbn(60929871);
grpc.unary(BookService.GetBook, {
  request: getBookRequest,
  host: host,
  onEnd: res => {
    const { status, statusMessage, headers, message, trailers } = res;
    if (status === grpc.Code.OK && message) {
      console.log("all ok. got book: ", message.toObject());
    }
  }
});

以上是官方给出的例子,发送一个标准请求。看到 callback 和一堆引入的文件的时候,我瞬间整个人就不好了,遂开始琢磨如何二次封装 gRPC 请求。

首先可以先从 callback 函数转成 Promise 下手:

// callbackToPromise.js
const promiseFunc = new Promise((resolve, reject) => {
  grpc.unary(BookService.GetBook, {
    request: getBookRequest,
    host: host,
    onEnd: res => {
      const { status, statusMessage, message } = res;
      if (status === grpc.Code.OK && message) {
        resolve(res)
      } else {
        reject(res);
      }
    }
  });
});

return promiseFunc;

我们还可以再各这个请求加上超时限制(折腾一下准没错):

// callbackToPromise.js
return utils.fetchTimeout(promiseFunc, 2000).catch(err => { // 设置 2000 ms 超时
  if (err.code === "TIMEOUT") {
    // 提示超时
  }
});

// utils.js
/**
 * fetch 超时 helper
 *
 * @param {Function} fetchPromise fetch 方法
 * @param {Number} timeout 超时时间
 * @returns Promise
 */
function fetchTimeout (fetchPromise, timeout) {
  let abortFunc = null;
  const abortPromise = new Promise((resolve, reject) => {
    abortFunc = () => {
      reject({ code: "TIMEOUT", msg: "TIMEOUT" });
    };
  });

  const abortablePromise = Promise.race([
    fetchPromise,
    abortPromise
  ]);

  setTimeout(() => {
    abortFunc(path);
  }, timeout);

  return abortablePromise;
}

这样 callback 函数专成 Promise 就完成了。

其次我们需要将 grpc-web-client 目标文件引入和回调函数的封装分割开来,这样也有利于之后代码的维护:

// user.js

/**
 * 根据用户 ID 查询用户信息
 *
 * @param {String} publicId 用户 ID
 */
export function queryUserDetails (publicId) {
  const queryUserDetailsRequest = new QueryUserDetailsRequest();
  queryUserDetailsRequest.setUserPublicId(publicId);

  const config = {
    request: queryUserDetailsRequest,
    headers: {
      ...headers,
      ...makeAuthorizationHeader(utils.getToken())
    }
  };

  return createRequest(Dashboard.QueryUserDetails, config, transformQueryUserDetailsValue);
}
// api.config.js
/**
 * 创建请求
 *
 * @param {Object} service service function
 * @param {Object} config 配置项
 * @param {Function} transformValue 响应数据体转换
 * @returns Promise
 */
export function createRequest (service, config, transformValue) {
  const promiseFunc = new Promise((resolve, reject) => {
    ProgressBar.start();
    grpc.unary(service, {
      request: config.request,
      host: DASHBOARD_API,
      metadata: new grpc.Metadata(config.headers),
      onEnd: (res) => {
        const { status, statusMessage, message } = res;
        if (status === grpc.Code.OK && message) {
          ProgressBar.finish();
          resolve((transformValue && transformValue(message.toObject())) || message.toObject()); // 在这里我们可以运行数据转化函数
        } else if (status === grpc.Code.Unauthenticated) {
          ProgressBar.fatal();
          errorHandler.showNotice(grpc.Code[status], statusMessage);
          router.push({
            name: "unauthenticated",
            path: "/403"
          });
          reject(res);
        } else {
          ProgressBar.fatal();
          errorHandler.showNotice(grpc.Code[status], statusMessage);
          reject(res);
        }
      }
    });
  });

  return utils.fetchTimeout(promiseFunc, `${service.service.serviceName}.${service.methodName}`, TIMEOUT).catch(err => {
    if (err.code === "TIMEOUT") {
      const { code, msg } = err;
      ProgressBar.fatal();
      errorHandler.showNotice(code, msg);
    }
  });
}

代码很简单,经过以上两步骤,我们就可以如下轻松加愉快的去请求数据了:

import { queryUserDetails } from "@/api/user";

queryUserDetails(publicId)
  .then(res => console.log(res))
  .catch(res => console.log(res));

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108675.html

相关文章

  • 明星分分合合的洪荒点击量,微博Mesh服务化改造如何支撑?(附PPT下载)

    摘要:为了解决这一系列问题,微博从年开发了语言的框架,并基于此完成了服务化改造。这些经历之下微博也积累了一套服务治理型的服务化体系。的版,所要解决的是微博平台内部服务之间的调用,因此协议时,其实并没有考虑到跨语言的问题,用的是对比较友好的。 showImg(https://segmentfault.com/img/remote/1460000012601596?w=1080&h=606); ...

    ShowerSun 评论0 收藏0
  • 带入gRPC:让你的服务同时提供 HTTP 接口

    摘要:带入让你的服务同时提供接口原文地址带入让你的服务同时提供接口项目地址前言接口需要提供给其他业务组访问,但是协议不同无法内调,对方问能否走接口,怎么办微信公众号小程序等第三方回调接口只支持接口,怎么办我相信你在实际工作中都会遇到如上问题,在中 带入gRPC:让你的服务同时提供 HTTP 接口 原文地址:带入gRPC:让你的服务同时提供 HTTP 接口项目地址:https://github...

    LiangJ 评论0 收藏0
  • 华尔街见闻基于istio的服务网格实践

    摘要:,托管于腾讯云容器平台容器编排工具。适配我们目前的服务部署在腾讯云托管,节点使用核的网络增强型机器,所有的后端服务都以部署,集群外部署高可用支持集群内服务发现,数据库以为主,消息队列采用。 距离2017年的见闻技术架构调整接近2年,随着业务线的发展,见闻技术部的项目数量、项目架构类型、基础设施规模、服务变更频率都在不断地增长,带给SRE的挑战是如何能更快地助力于开发人员更快更稳定地部署...

    stonezhu 评论0 收藏0
  • gRPC+gRPC Gateway 能不能不用证书?

    摘要:原文地址能不能不用证书过去为什么不行因为仅支持标识,而标识必须使用传输层安全性的协议,此标识符用于应用层协议协商字段以及识别。 如果你以前有涉猎过 gRPC+gRPC Gateway 这两个组件,你肯定会遇到这个问题,就是 为什么非得开 TLS,才能够实现同端口双流量,能不能不开? 又或是 我不想用证书就实现这些功能,行不行?。我被无数的人问过无数次这些问题,也说服过很多人,但说服归说...

    yanest 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<