前端多并发思考

482 阅读4分钟

前端开发日常要做的一件事就是发请求。 有些页面业务逻辑复杂,要发送的请求较多;为了能够快速获取接口响应结果,展示页面内容,这时不免就需要进行并发请求。

场景假设

现在有个页面在初始化时,要调用很多数据接口,并且有些接口之间有依赖(先后)的关系。

数据接口

  1. requestA 没有前置依赖
  2. requestA1 依赖requestA的响应结果
  3. requestA2 依赖requestA1的响应结果
  4. requestA3 依赖requestA1的响应结果
  5. requestB 无依赖
  6. requestB1 依赖requestB的响应结果
  7. requestC 依赖requestA2/requestA3和requestB1的响应结果
  8. requestD 依赖requestC
  9. requestD1 依赖requestD
  10. requestE 无依赖

一般并发写法

{
  mounted() {
    const p1 = new Promise(resolve => {
      requestA().then(ARes => {
        requestA1(ARes).then(A1Res => {
          if (A1Res.status === 1) requestA2(A1Res).then(resolve)
          else requestA3(A1Res).then(resolve)
        })
      })
    })
    
    const p2 = new Promise(resolve => {
      requestB().then(BRes => {
        requestB1(BRes).then(resolve)
      })
    })
    
    Promise.all([p1, p2]).then(items => {
      requestC(items).then(CRes => {
        requestD(CRes).then(DRes => {
          requestD1(DRes)
        })
      })
    })
    
    requestE()
  }
}
  1. 现在需要进行一些调整。requestB1不再依赖requestB,可以直接请求;requestC依赖requestA2/requestA3、requestB、requestB1的响应结果。代码进行如下改动:
{
  mounted() {
    const p1 = new Promise(resolve => {
      requestA().then(ARes => {
        requestA1(ARes).then(A1Res => {
          if (A1Res.status === 1) requestA2(A1Res).then(resolve)
          else requestA3(A1Res).then(resolve)
        })
      })
    })
    
    Promise.all([p1, requestB, requestB1]).then(items => {
      requestC(items).then(CRes => {
        requestD(CRes).then(DRes => {
          requestD1(DRes)
        })
      })
    })
    
    requestE()
  }
}
  1. 新增接口requestF,依赖requestD1、requestE。代码改动如下:
{
  mounted() {
    const p1 = new Promise(resolve => {
      requestA().then(ARes => {
        requestA1(ARes).then(A1Res => {
          if (A1Res.status === 1) requestA2(A1Res).then(resolve)
          else requestA3(A1Res).then(resolve)
        })
      })
    })
    
    const p2 = new Promise(resolve => {
      requestB().then(BRes => {
        requestB1(BRes).then(resolve)
      })
    })
    
    const p3 = new Promise(resolve => {
      Promise.all([p1, p2]).then(items => {
        requestC(items).then(CRes => {
          requestD(CRes).then(DRes => {
            requestD1(DRes).then(resolve)
          })
        })
      })
    })
    
    Promise.all([p3, requestE]).then(items => {
      requestE(items)
    })
  }
}

总结

接口变化、依赖关系变化,代码改动较大。

思考

采取一种写法,既可以方便应对接口间的依赖关系变化,又能够一目了然接口相互间的依赖关系。

// 伪代码
// 依赖关系
dependsMap = {
  requestA1: [requestA],
  [requestA2 | requestA3]: [requestA1],
  requestB1: [requestB],
  requestC: [requestA2 | requestA3, requestB1],
  requestD: [requestC],
  requestD1: [requestD]
}
// 无依赖
init([requestA, requestB, requestE])

由于dependsMap的key是一个对象,可以使用Map或数组来表示。推荐使用数组表示:

requestA2_or_reuqestA3(A1Res) {
  if (A1Res.status === 1) return reuqestA2()
  return requestA3()
}

depends = [
  [requestA1, [requestA]],
  [requestA2_or_requestA3, [requestA1]],
  [requestB1, [requestB]],
  [requestC, [requestA2_or_requestA3, requestB1]],
  [requestD, [requestC]],
  [requestD1, [requestD]]
]

requestB1不再依赖requestB;requestC依赖requestA2_or_requestA3、requestB、requestB1的响应结果。代码改动如下:

depends = [
  [requestA1, [requestA]],
  [requestA2_or_requestA3, [requestA1]],
  [requestC, [requestA2_or_requestA3, requestB, requestB1]],
  [requestD, [requestC]],
  [requestD1, [requestD]]
]

新增requestF, 依赖requestD1、requestE。

depends.push([requestF, [requestD1, requestE]])

代码实现

上面的实现方式,我将之称之为“事件依赖”。

首先要确定事件的类型,一种是普通的请求,返回值是一个Promise;另一种就是普通的函数(或方法),用来对请求结果进行处理。(如,requestA2_or_requestA3)

如何实现

使用状态管理的方式,将事件依赖变为依赖事件对应的状态。

举个栗子

如上,requestA、requestA1两个事件,首先注册这两个事件并设置它们间的依赖关系。

const events = [requestA, requestA1];
// 分别对应events中的事件
// 0表示事件未执行或不满足
const events_state = [0, 0];

const depends = [
  [requestA1, [requestA]]
];

事件requestA没有其它依赖,会立即执行,执行完成后,会修改对应位置的state;并且检测剩余未执行事件中有满足依赖状态的;此时检测到事件requestA1满足执行状态,则执行requestA1。

代码

class EventDepend {
  // 所有注册事件
  events = [];
  // 所有注册事件状态
  eventsState = [];
  // 事件依赖关系
  depends = {};
  
  // 注册事件
  register(event) {
    const events = Array.isArray(event) ? event : [event];
    events.forEach(event => {
      // 过滤已被注册的事件
      if (this.events.includes(event)) return;
      this.events.push(event);
      this.eventsState.push(0);
    })
  }
  
  // 设置依赖关系
  depend(event, dependEvents) {
    // 事件不能依赖自身,否则导致死循环
    if (dependEvents.includes(event)) {
      throw Error('事件不能依赖自身,将导致死循环');
    }
    // 注册事件
    this.register([event, ...dependEvents]);
    // 设置依赖状态关系
    const index = this.events.indexOf(event);
    this.depends[index] = dependEvents.map(
      event => this.dependEvents.indexOf(event)
    );
  }
  
  // 设置多个依赖关系
  dependMany(depends) {
    depends.forEach(opts => this.depend(...opts));
  }
  
  // 执行事件
  exec(event) {
    const events = Array.isArray(event) ? event : [event];
    // 注册事件
    this.register(events);
    events.forEach(event => {
      // 执行事件
      const result = event();
      // 事件有两种类型:普通函数或返回Promise
      // 普通函数类型,若返回值为false,表示不改变事件状态
      if (result === false) return;
      // 改变事件状态 & 执行满足状态依赖的事件
      // 如果是Promise类型,则在then中改变事件状态
      const isPromise = toString.call(result) === '[object Promise]';
      const changeState = this.changeState.bind(this, event);
      isPromise ? result.then(changeState) : changeState();
    });
  }
  
  // 改变事件状态 & 执行满足状态的依赖事件
  changeState(event) {
    // 改变事件状态
    const index = this.events.indexOf(event);
    this.eventsState[index] = 1;
    // 执行满足状态的依赖事件
    const fulfilledKeys = Object.keys(this.depends).filter(
      key => !this.depends[key].find(
        index => this.eventsState[index] === 0
      )
    );
    fulfilledKeys.forEach(key => {
      // 执行
      this.exec(this.events[key]);
      // 已执行,删除
      delete this.depends[key];
    });
  }
}

提升

  1. 使用单个数字管理状态(位运算)