javascript异步处理方案

93 阅读4分钟

callback回调函数

  • 优点:简单,容易理解。
  • 缺点:代码可读性差,不易维护,耦合度高,层层嵌套造成回调地狱。
const funcOne = (num, callback) => {
  const newNum = num + 5;
  // 异步操作,使用setTimeout模拟
  setTimeout(() => callback(newNum), 1000)
}
const funcTwo = (num, callback) => {
  const newNum = num + 10;
  // 异步操作,使用setTimeout模拟
  setTimeout(() => callback(newNum), 2000)
}
const funcThree = (num, callback) => {
  const newNum = num + 20;
  // 异步操作,使用setTimeout模拟
  setTimeout(() => callback(newNum), 3000)
}

const start = () => {
  const num = 5;
  console.log(num);
  funcOne(num, function(funcOneReturnvalue) {
    console.log(funcOneReturnvalue);
    funcTwo(funcOneReturnvalue, function(funcTwoReturnvalue) {
      console.log(funcTwoReturnvalue);
      funcThree(funcTwoReturnvalue, function(funcThreeReturnvalue) {
        console.log(funcThreeReturnvalue);
      });
    });
  });
}

start();

事件监听(发布订阅模式)

  • 优点:更符合模块化思想,编写自己的监听器的时候可以做很多优化,从而更好的监听程序的运行。
  • 缺点:整个程序变成了事件驱动,或多或少影响了流程,而且每次使用都要注册事件监听器然后触发,比较麻烦。
document.body.addEventListener('click', function () {
  console.log('click');
});

// 实现发布订阅
type EventHandler = (data?: any) => void;

class EventEmitter {
  handlersMapping: {
    [key: string]: Array<EventHandler>;
  } = {};

  // 注册事件和处理函数
  on(type: string, handler: EventHandler) {
    let handlers = this.handlersMapping[type];
    if (!handlers) {
      handlers = this.handlersMapping[type] = [];
    }
    handlers.push(handler);
  }

  // 销毁事件和处理函数
  off(type: string, handler: EventHandler) {
    let handlers = this.handlersMapping[type] || [];
    if (!handler) {
      // 没有传入要销毁的方法时,清空时间对应所有方法
      this.handlersMapping[type] = [];
    } else {
      const targetIndex = handlers.findIndex((handlerItem: EventHandler) => handlerItem === handler);
      if (targetIndex !== -1) {
        handlers.splice(targetIndex, 1);
      }
    }
  }

  // 触发某事件所有回调并带参数
  emit(type: string, payload?: any) {
    let handlers = this.handlersMapping[type] || [];
    handlers.forEach(handler => handler(payload));
  }
}

export default new EventEmitter();

// 内部组件进行监听某个事件执行在内部组件中注册处理函数,在全局组件中满足条件时触发内部组件所监听的方法
const dyxtest = () => {}
useEffect(() => {
  eventBus.on("dyxtest", dyxtest);
  return () => {
    eventBus.off("dyxtest", dyxtest);
  };
}, []);

// 全局组件触发
eventBus.emit("dyxtest");

Promise

  • 优点:避免了回调函数层层嵌套,可读性更强。链式操作,可以在then中继续写Promise对象并return,然后继续调用then进行回调操作。
  • 缺点:Promise对象一旦创建就会立即执行,不能中途取消。
const funcOne = num => {
  return new Promise((resolve, reject) => {
    const newNum = num + 5;
    setTimeout(() => resolve(newNum), 1000)
  })
}

const funcTwo = num => {
  return new Promise((resolve, reject) => {
    const newNum = num + 10;
    setTimeout(() => resolve(newNum), 2000)
  })
}

const funcThree = num => {
  return new Promise((resolve, reject) => {
    const newNum = num + 20;
    setTimeout(() => resolve(newNum), 3000)
  })
}

const start = () => {
  const num = 5;
  console.log(num);
  funcOne(num).then(resOne => {
    console.log(resOne);
    return funcTwo(resOne);
  }).then(resTwo => {
    console.log(resTwo);
    return funcThree(resTwo);
  }).then(resThree => {
    console.log(resThree);
  });
}

start();

Generator函数

  • 优点:优雅的流程控制方法,允许函数被中断地执行。
  • 缺点:Generator函数的执行必须依赖执行器,对于只做异步处理还是不太方便。
const funcOne = () => {
  return new Promise(resolve => {
    setTimeout(() => console.log(1), 1000);
  })
}

const funcTwo = () => {
  return new Promise(resolve => {
    setTimeout(() => console.log(2), 2000);
  })
}

const funcThree = () => {
  return new Promise(resolve => {
    setTimeout(() => console.log(3), 3000);
  })
}

function* start() {
  console.log('start');
  yield funcOne();
  yield funcTwo();
  yield funcThree();
  return 'dyx';
}

const startFun = start();  // 仅生成指针对象,并不执行
startFun.next() // start 1
startFun.next() // 2
startFun.next() // 3

async函数

  • 优点:内置执行器,语义更好,适用性更广。
  • 缺点:误用await可能会导致性能问题,因为await会阻塞代码。
const funcOne = num => {
  return new Promise((resolve, reject) => {
    const newNum = num + 5;
    setTimeout(() => resolve(newNum), 1000)
  })
}

const funcTwo = num => {
  return new Promise((resolve, reject) => {
    const newNum = num + 10;
    setTimeout(() => resolve(newNum), 2000)
  })
}

const funcThree = num => {
  return new Promise((resolve, reject) => {
    const newNum = num + 20;
    setTimeout(() => resolve(newNum), 3000)
  })
}

const start = async () => {
  const num = 5;
  console.log(num);
  const firstAddNum = await funcOne(num);
  console.log(firstAddNum);
  const secondAddNum = await funcTwo(firstAddNum);
  console.log(secondAddNum);
  const thirdAddNum = await funcThree(secondAddNum);
  console.log(thirdAddNum);
}

start();

async/await对比promise的优缺点

  • 优点
    1. 真正的串行的同步写法,代码阅读相对容易。
    2. 对于条件语句和其他流程语句比较友好,可以直接写到判断条件里面。
  • 缺点
    1. 无法处理promise返回的reject对象,要借助try catch。

async/await与Generator的差异

  • async/await自带执行器,不需要手动调用next()就能自动执行下一步。
  • async函数返回值是Promise对象,而Generator返回的是生成器对象。
  • await能够返回Promise的resolve/reject的值。

利用Generator实现async函数

async/await实际上是对Generator(生成器)的封装,是一个语法糖。通过step函数实现Generator函数的自执行,当Generator函数的.next()执行返回done: true时表示执行完毕.

function asyncToGenerator(generatorFunc) {
  return function() {
    const gen = generatorFunc.apply(this, arguments);
    return new Promise((resolve, reject) => {
      function step(key, arg) {
        let generatorResult;
        try {
          generatorResult = gen[key](arg);
        } catch (error) {
          return reject(error);
        }
        const { value, done } = generatorResult;
        if (done) {
          return resolve(value);
        } else {
          return Promise.resolve(value).then(val => step('next', val), err => step('throw', err));
        }
      }
      step("next");
    })
  }
}