blog icon indicating copy to clipboard operation
blog copied to clipboard

async/await配合for循环的异步串行请求

Open mrdulin opened this issue 7 years ago • 0 comments

问题: 现有模块id数组moduleIds,通过模块id请求模块数据,并且,后一个模块的接口请求依赖前一个模块的数据。当任意一个接口请求失败,则终止后续的请求。

实现一个函数,返回结果包含请求成功模块的数据和请求失败模块的错误数据。

interface IApiResponse {
  message: string;
  errorCode: number;
  dataBePassedToNextModule?: string;
}
interface IApiResponseWithId extends IApiResponse {
  id: number;
}

interface IExtraData {
  info: string;
}

interface INgOnInit {
  ngOnInit(): void;
}

interface IViewModel {
  id: number;
  message: string;
}

interface IViewModelById {
  [id: number]: IViewModel;
}

const coin = (): boolean => Math.random() < 0.5;
const apiErrorSwitch: string = 'on';
//模拟传递给下一次模块请求的依赖数据
const mockDatasBePassedToNextModule: string[] = ['react', 'angular', 'jquery'];
//模拟api请求
const fetch = (id: number, data?: string | undefined): Promise<IApiResponse> => new Promise((resolve, reject) => {
  const coinValue: boolean = coin();
  let result: IApiResponse;
  // console.log('coinValue: ', coinValue);
  const baseMessage: string = `请求module:${id}数据成功`;
  setTimeout(() => {
    const successData: IApiResponse = {
      message: data ? `${baseMessage}|${data}` : baseMessage,
      errorCode: 0,
      dataBePassedToNextModule: mockDatasBePassedToNextModule[id - 1]
    };
    const errorData: IApiResponse = {
      message: `请求module:${id}数据失败`,
      errorCode: -100
    };
    if (apiErrorSwitch === 'on') {
      result = coinValue ? successData : errorData;
    } else {
      result = successData;
    }
    if (result.errorCode) {
      reject(result);
    }
    resolve(successData);
  }, 1000);
});
//fetch一个模块
const fetchModule = async (id: number, data?: string | undefined): Promise<IApiResponseWithId> => {
  try {
    const result: IApiResponse = await fetch(id, data);
    const resultWithId: IApiResponseWithId = Object.assign({ id }, result);
    return resultWithId;
  } catch (errObj) {
    errObj.id = id;
    delete errObj.errorCode;
    errObj.extraData = { info: '前端附加错误信息' };
    throw errObj;
  }
};

function fetchFirstModule(idx: number): boolean {
  return idx === 0;
}
//fetch多个模块
async function fetchModulesByIds(modIds: number[]): Promise<IViewModelById> {
  const moduleById: IViewModelById = {};
  let module: IApiResponseWithId, dataBePassedToNextModule: string | undefined;
  let error: any;
  for (let i: number = 0, moduleIdCount: number = modIds.length; i < moduleIdCount; i++) {
    const moduleId: number = modIds[i];
    if (error) break;
    try {
      console.log(Date.now());
      if (dataBePassedToNextModule || fetchFirstModule(i)) {
        module = await fetchModule(moduleId, dataBePassedToNextModule);
        dataBePassedToNextModule = module.dataBePassedToNextModule;
        const vm: IViewModel = getViewModelByModule(module);
        moduleById[vm.id] = vm;
      }
    } catch (err) {
      error = err;
      moduleById[err.id] = err;
    }
  }
  return moduleById;
}

function getViewModelByModule(mod: IApiResponseWithId): IViewModel {
  const vm: IViewModel = { id: -1, message: '' };
  vm.id = mod.id;
  vm.message = mod.message;
  return vm;
}

class Component implements INgOnInit {
  public vm: any = {
    1: {},
    2: {},
    3: {}
  };
  private moduleIds: number[] = [1, 2, 3];
  constructor() {
    this.ngOnInit();
  }

  public async ngOnInit() {
    //fetch一个模块
    try {
      const moduleOne = await fetchModule(1);
      console.log('moduleOne: ', moduleOne);
    } catch (e) {
      console.log('moduleOne error:', e);
    }

    //fetch多个模块
    const moduleById = await fetchModulesByIds(this.moduleIds);
    this.vm = Object.assign(this.vm, moduleById);
    console.log('render vm: ', this.vm);
  }
}

const myFuckingComponent = new Component();

export { };

当然,服务器端API接口,可以改造成或者新定义一个接收模块id数组作为参数的接口,这样比较灵活,不论前端需要一个模块,还是多个模块,只需要给定对应的模块id数组即可。前端的场景根据产品的要求可谓是多种多样,例如,首页模块比较多,所有模块都要懒加载;或者是首次请求加载3个模块,后续模块懒加载等。

mrdulin avatar Nov 13 '17 09:11 mrdulin