面试题:实现小程序平台的并发双工 Rpc 通信

开发 架构 移动应用
rpc 是远程过程调用,是跨进程、跨线程等场景下通信的常见封装形式。面试题是小程序平台的双线程的场景,在一个公共文件里实现双工的并发的 rpc 通信。

[[435138]]

前几天面试的时候遇到一道面试题,还是挺考验能力的。

题目是这样的:

rpc 是 remote procedure call,远程过程调用,比如一个进程调用另一个进程的某个方法。很多平台提供的进程间通信机制都封装成了 rpc 的形式,比如 electron 的 remote 模块。

小程序是双线程机制,两个线程之间要通信,提供了 postMessage 和 addListener 的 api。现在要在两个线程都会引入的 common.js 文件里实现 rpc 方法,支持并发的 rpc 通信。

达到这样的使用效果:

  1. const res = await rpc('method', params); 

这道题是有真实应用场景的题目,比一些逻辑题和算法题更有意思一些。

实现思路

两个线程之间是用 postMessage 的 api 来传递消息的:

  • 在 rpc 方法里用 postMessage 来传递要调用的方法名和参数
  • 在 addListener 里收到调用的时候,调用 api,然后通过 postMessage 返回结果或者错误

我们先实现 rpc 方法,通过 postMessage 传递消息,返回一个 promise:

  1. function rpc(method, params) { 
  2.     postMessage(JSON.stringify({ 
  3.         method, 
  4.         params  
  5.     })); 
  6.  
  7.     return new Promise((resolve, reject) => { 
  8.          
  9.     }); 

这个 promise 什么时候 resolve 或者 reject 呢?是在 addListener 收到消息后。那就要先把它存起来,等收到消息再调用 resolve 或 reject。

为了支持并发和区分多个调用通道,我们加一个 id。

  1. let id = 0; 
  2. function genId() { 
  3.     return ++id; 
  4.  
  5. const channelMap = new Map(); 
  6.  
  7. function rpc(method, params) { 
  8.     const curId = genId(); 
  9.  
  10.     postMessage(JSON.stringify({ 
  11.         id: curId, 
  12.         method, 
  13.         params  
  14.     })); 
  15.  
  16.     return new Promise((resolve, reject) => { 
  17.         channelMap.set(curId, { 
  18.             resolve, 
  19.             reject 
  20.         }); 
  21.     }); 

这样,就通过 id 来标识了每一个远程调用请求和与它关联的 resolve、reject。

然后要处理 addListener,因为是双工的通信,也就是通信的两者都会用到这段代码,所以要区分一下是请求还是响应。

  1. addListener((message) => { 
  2.     const { curId, method, params, res}= JSON.parse(message); 
  3.     if (res) { 
  4.         // 处理响应 
  5.     } else { 
  6.         // 处理请求 
  7.     } 
  8. }); 

处理请求就是调用方法,然后返回结果或者错误:

  1. try { 
  2.     const data = global[method](...params); 
  3.     postMessage({ 
  4.         id 
  5.         res: { 
  6.             data 
  7.         } 
  8.     }); 
  9. } catch(e) { 
  10.     postMessage({ 
  11.         id, 
  12.         res: { 
  13.             error: e.message 
  14.         } 
  15.     }); 

处理响应就是拿到并调用和 id 关联的 resolve 和 reject:

  1. const { resolve, reject  } = channelMap.get(id); 
  2. if(res.data) { 
  3.     resolve(res.data); 
  4. else { 
  5.     reject(res.error); 

全部代码是这样的:

  1. let id = 0; 
  2. function genId() { 
  3.     return ++id; 
  4.  
  5. const channelMap = new Map(); 
  6.  
  7. function rpc(method, params) { 
  8.     const curId = genId(); 
  9.  
  10.     postMessage(JSON.stringify({ 
  11.         id: curId, 
  12.         method, 
  13.         params  
  14.     })); 
  15.  
  16.     return new Promise((resolve, reject) => { 
  17.         channelMap.set(curId, { 
  18.             resolve, 
  19.             reject 
  20.         }); 
  21.     }); 
  22.  
  23. addListener((message) => { 
  24.     const { id, method, params, res}= JSON.parse(message); 
  25.     if (res) { 
  26.         const { resolve, reject  } = channelMap.get(id); 
  27.         if(res.data) { 
  28.             resolve(res.data); 
  29.         } else { 
  30.             reject(res.error); 
  31.         } 
  32.     } else { 
  33.         try { 
  34.             const data = global[method](...params); 
  35.             postMessage({ 
  36.                 id 
  37.                 res: { 
  38.                     data 
  39.                 } 
  40.             }); 
  41.         } catch(e) { 
  42.             postMessage({ 
  43.                 id, 
  44.                 res: { 
  45.                     error: e.message 
  46.                 } 
  47.             }); 
  48.         } 
  49.     } 
  50. }); 

我们实现了最开始的需求:

  • 实现了 rpc 方法,返回一个 promise
  • 支持并发的调用
  • 两个线程都引入这个文件,支持双工的通信

其实主要注意的有两个点:

  • 要添加一个 id 来关联请求和响应,这在 socket 通信的时候也经常用
  • resolve 和 reject 可以保存下来,后续再调用。这在请求取消,比如 axios 的 cancelToken 的实现上也有应用

这两个点的应用场景还是比较多的。

总结

rpc 是远程过程调用,是跨进程、跨线程等场景下通信的常见封装形式。面试题是小程序平台的双线程的场景,在一个公共文件里实现双工的并发的 rpc 通信。

思路文中已经讲清楚了,主要要注意的是 promise 的 resolve 和 reject 可以保存下来后续调用,通过添加 id 来标识和关联一组请求响应。

 

责任编辑:姜华 来源: 神光的编程秘籍
相关推荐

2009-08-11 15:09:44

一道面试题C#算法

2023-08-07 14:29:26

模拟电话全双工通信

2020-06-04 14:40:40

面试题Vue前端

2014-09-19 11:17:48

面试题

2021-07-04 08:01:30

Synchronize线程安全并发编程

2019-06-05 07:47:32

Nginx高并发多线程

2015-09-10 08:46:15

Java面试题

2020-04-07 14:40:19

Java并发编程多线程

2011-03-24 13:27:37

SQL

2020-04-12 22:29:50

程序员MySQL数据

2023-11-13 07:37:36

JS面试题线程

2012-05-25 10:15:06

Java程序员面试题

2022-07-27 08:27:34

Call前端

2015-09-02 09:32:56

java线程面试

2009-06-06 18:36:02

java面试题

2009-06-06 18:34:05

java面试题

2019-09-20 14:25:21

程序员Google人生第一份工作

2020-09-21 11:10:06

Docker运维面试

2010-11-26 10:53:29

戴尔

2014-07-15 11:10:01

面试题面试
点赞
收藏

51CTO技术栈公众号