Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

解决全局回调形式通信时连续触发通信时的全局方法覆盖问题 #15

Open
zhangyu921 opened this issue Jun 9, 2021 · 0 comments

Comments

@zhangyu921
Copy link
Owner

这个问题可能比较冷门,我遇到的是在混合开发的场景,与 App 通信的情况。

一般的步骤是:前端 JS 先准备好全局回调的逻辑,例如 window.setUserData = ( result ) => { /_ Do something _/ }。然后通过某种方式触发,假设类似是:window.postmessages.getUserData()。

这种情况下,前端一般会封装一个方法:

function getUserData() {
  new Promise((resolve, reject) => {
    window.setUserData = (result) => {
      resolve(result)
    }
    window.postmessage.getUserData()
  }
}

这会导致一个问题,当同时调用getUserData 两次的时候,就会发生第一个getUserData就彻底失联的情况。

要解决这个问题,我想到首先需要代理全局方法:

const realSetUserData = genGlobalCbAdaptor('setUserData');

并且,需要在多次调用的时候,前面的回调还未返回时,后面的请求需要先等待。于是……我想到了await。

改写一下:

const realSetUserData = genGlobalCbAdaptor('setUserData');

function getUserData() {
  new Promise(async (resolve, reject) => {
    // 这里的 await 实际在等待这个回调被真正的写到了全局
    await realSetUserData((result) => {
      resolve(result)
    })
    window.postmessage.getUserData()
  }
}

这样形态上就实现了在连续调用 getUserData 的时候,在触发之前保持全局回调的顺序。

此时出现了新的问题,向App触发 window.postmessage.getUserData() 后,有可能 App 压根不回调全局方法。

这需要实现一个超时机制,在触发后迟迟收不到回调时取消此次。因为不是所有通信都是有可能不回调的,所以让代理返回一个取消方式,外界进行超时判断。

再调整一下:

function getUserData() {
  new Promise(async (resolve, reject) => {
    let timer;
    const cancel = await realSetUserData((result) => {
      clearTimer(timer)
      resolve(result)
    })
    timer = setTimeout(() => {
      reject('time out')
      cancel()
    })
    window.postmessage.getUserData()
  }
}

看起来有点奇怪,timer的声明和赋值不在同一处,但似乎也是合理的,超时的开始是需要在全局回调函数已经被设置好了之后,而全局的回调又需要清除定时器,只能使用let提早声明了。

这样就差不多了,最后就是 genGlobalCbAdaptor 到底怎么实现呢?下面就是了:

function genGlobalCbAdaptor(
  funcName: string
): (func: Function) => Promise<() => void> {
  let processing = false;
  const cbQue = [];
  window[funcName] = doNothing;

  async function consume() {
    processing = true;
    while (cbQue.length > 0) {
      const cb = cbQue.shift();
      const { func, resolve } = cb;
      const loopPromise = new Promise((complete) => {
        const realCB = function (...args: any[]) {
          complete(null);
          return func(...args);
        };
        window[funcName] = realCB;
        resolve(complete);
      });
      // 等待全局方法被使用
      await loopPromise;
      window[funcName] = doNothing;
    }
    processing = false;
  }

  return (func: Function) => {
    const p = new Promise<any>((resolve, reject) => {
      cbQue.push({
        resolve,
        reject,
        func,
      });
    });
    if (!processing) {
      consume();
    }
    return p;
  };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant