ts-protoc-gen
不支持瀏覽器環境首先你應該瞭解 ts-protoc-gen,它的目標是將編譯 .proto
文件所生成的文件夾包含 .js
和 .d.ts
文件。vue
可是...node
<div class="tip">
請不要將 ts-protoc-gen
生成的代碼直接用在瀏覽器中
</div>git
由於當咱們直接使用以下代碼時:github
import { MyMessage } from "../generated/users_pb"; const msg = new MyMessage(); msg.setName("John Doe");
報錯:web
Uncaught ReferenceError: exports is not defined.
這個錯誤應該不陌生,exports
未定義,多見於瀏覽器環境直接使用 node
環境代碼,因此我翻看了一下 ts-protoc-gen 的源碼,直到發現了以下代碼:api
printer.printLn(`exports.${service.name} = ${service.name};`);
這裏的 exports
就已經說明一切了,這個庫生成的是運行在 node
環境的 CommonJS
規範代碼,而對於使用 Webpack4
的 vue project
項目,也並不支持混合使用模塊系統,因此目前我想到了個臨時解決方案:promise
// 編譯文件導出方法和類時強制使用 `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
請求。bash
首先能夠先從 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));