JavaScript异步编程

415 阅读3分钟

回调

/* 单个异步操作 */
request('/a', res => {
  // todo
});
/* 多异步协作 串行 */
function requestA(cb) {
  request('/a', cb);
}

function requestB(params, cb) {
  request('/b', params, cb);
}

requestA(params => {
  requestB(params, res => {
    // todo
  });
});
/* 多异步协作 并行 */
let times = 2;
let result = {};

function done(action, data) {
  result[action] = data;

  if (!--times) {
    // todo
  }
}

request('/a', res => {
  done('a', res);
});

request('/b', res => {
  done('b', res);
});

订阅/发布

let slice = Array.prototype.slice;
let uid = 0;

let proto = {
  /**
   * @private
   * @param {String} name 事件名称
   * @param {Function} cb 回调函数 
   * @description 添加相关事件的回调函数
   */
  _on(name, cb) {
    let events = this.events;
    let cbs = events[name] || (events[name] = []);
    
    cbs.push(cb);
  },

  /**
   * @public
   * @description 添加一次性事件函数 .once('name1', 'name2', function() {})
   */
  once() {
    let args = slice.call(arguments);
    let cb = args.pop();
    let times = args.length;
    let map = {};

    let fn = (name, data) => {
      map[name] = data;

      if (!(--times)) {
        cb(map);
        this.off(name, fn);
      }
    };

    fn.__uid__ = cb.__uid__ = uid++;

    args.forEach(name => {
      this._on(name, fn);
    });
  },

  /**
   * @public
   * @param {String} name 事件名称
   * @param {Function} cb 回调函数
   * @description 解绑相关事件
   */
  off(name, cb) {
    let events = this.events;
    let cbs = events[name];

    if (cbs) {
      for (let i = 0; i < cbs.length; i++) {
        if (cb.__uid__ === cbs[i].__uid__) {
          cbs.splice(i--, 1);
        }
      }
    }
  },

  /**
   * @public
   * @param {String} name 事件名称 
   * @param {Any} data 回调参数
   * @description 触发相关回调
   */
  emit(name, data) {
    let events = this.events;
    let cbs = events[name];

    if (cbs) {
      cbs = cbs.slice();

      cbs.forEach(cb => cb(name, data));
    }
  }
};

// 初始化
let emiter = Object.create(proto, {
  events: {
    value: []
  }
});
/* 多异步协作 串行 */
emiter.once('a', ret => {
  let params = ret.a;

  request('/b', params, res => {
    // todo
  });
});

request('/a', res => {
  emiter.emit('a', res);
});
/* 多异步协作 并行 */
emiter.once('a', 'b', ret => {
  let { a, b } = ret;

  // todo
});

request('/a', res => {
  emiter.emit('a', res);
});

request('/b', res => {
  emiter.emit('b', res);
});

Deferred/Promise

$.Deferred方法,返回一个deferred对象;该对象拥有一个promise方法,返回一个promise对象。两者的区别在于deferred对象调用resolve或者reject方法。promise存在三种状态:pending , resolved, rejected

  • then: 接受resolved, rejected, progress三个回调作为参数,返回一个新的promise对象,该promise对象的状态取决于相应回调的返回值,通常用于实现多异步串行或者返回一个新值供后续使用
  • done: 添加resolved回调
  • fail: 添加rejected回调
  • always: 同时添加resolvedrejected的回调
// 使用例子
function delay(timeout) {
  let deferred = $.Deferred();

  setTimeout(_ => deferred.resolve(), timeout);

  return deferred.promise();
}

delay(3 * 1000)
  .done(_ => {
    console.log('这里是3秒后触发');
  });

delay(3 * 100)
  .then(_ => delay(3 * 1000))
  .done(_ => {
    console.log('这里6秒后触发');
  });
/* 单任务异步请求 */
$.get('/a')
  .done(res => {
    // todo succeed
  })
  .fail(e => {
    // todo failed
  });
/* 多任务串行 */
$.get('/a')
  .then(resA => $.get('/b'))
  .done(resB => {
    // todo succeed
  })
  .fail(e => {
    // todo failed
  });
/* 多任务并行 */
$.when($.get('/a'), $.get('/b'))
  .done((resA, resB) => {
    // todo all succeed
  })
  .fail(e => {
    // todo one failed
  })
  .always(_ => {
    // todo whatever
  });

宏任务(macrotask)与微任务(microtask)

JavaScript的事件队列分为两种:宏任务与微任务。宏任务每次从事件队列取出一个来执行;当当前宏任务执行完毕后,再从微任务的事件队列中取出所有微任务来执行。

  • macrotask:包括整体代码script, setTimeout, setInterval, setImmediate
  • microtaskPromise, process.nextTick, Mutation Observer, Message Channel

setTimeout(_ => console.log(1), 0);

Promise.resolve()
  .then(_ => console.log(2))
  .then(_ => console.log(3));

console.log(4);

ES6 Promise

Promisethencatch为微任务,执行顺序比setTimeout优先,可以看作浏览器端的process.nextTick。同时Vue.nextTick也是优先基于Promise来实现。promise存在三种状态:pending , resolved, rejected

  • then: 接受resolved, rejected两个回调函数作为参数,返回一个新的promise对象,该对象的状态取决于相对应回调的返回值
  • catch: 接受rejected回调函数作为参数,返回一个新的promise对象,该对象的状态取决于回调的返回值
Promise.resolve()
  .then(_ => Promise.reject())
  .then(_ => console.log('then'))
  .catch(_ => console.log('reject'))
  //.then(_ => console.log('always'));
/* 单任务异步请求 */
axios.get('/a')
  .then(res => {
    // todo succeed
  })
  .catch(e => {
    // todo failed
  });
/* 多任务串行 */
axios.get('/a')
  .then(resA => axios.get('/b'))
  .then(resB => {
    // todo succeed
  })
  .catch(e => {
    // todo failed
  });
/* 多任务并行 */
Promise.all([
    axios.get('/a'), 
    axios.get('/b')
  ])
  .then(([resA, resB]) => {
    // todo all succeed
  })
  .catch(e => {
    // todo one failed
  })
  .then(_ => {
    // todo alway
  });

Generator

Promise配合Generator,真正的同步写法实现异步功能来临!!!

function so(fn) {
  return function() {
    let args = Array.prototype.slice.call(arguments, 0);

    return new Promise((resolve, reject) => {
      let generator = fn.apply(null, args);

      function onFullfilled(ret) {
        if (ret.done) {
          return resolve(ret.value);
        }

        let promise = ret.value;

        if (!(promise instanceof Promise)) {
          promise = Promise.resolve(promise);
        }

        promise
          .then(res => {
            try {
              onFullfilled(generator.next(res));
            } catch(e) {
              reject(e);
            }
          })
          .catch(e => {
            try {
              generator.throw(e);
            } catch(e) {
              reject(e);
            }
          });
      }

      try {
        onFullfilled(generator.next());
      } catch(e) {
        reject(e);
      }
    });
  }
}

let i = 0;

function delay(timeout) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, timeout, i++);
  });
}

function *go() {
  let v1 = yield delay(3 * 1000);
  console.log(`3s后输出: ${v1}`);
  let v2 = yield delay(3 * 1000);
  console.log(`6s后输出: ${v2}`);
  return yield delay(3 * 1000);
}

so(go)().then(v => console.log(`9s后输出: ${v}`)).catch(e => console.log(e.message));
/*多异步串行*/
function *request() {
  let r1 = yield axios.get('/a');
  let r2 = yield aixos.get('/b', { params: { id: r1.id } });

  // todo
}

so(request)().catch(e => {});
/*多异步并行*/
function *request() {
  let [r1, r2] = yield Promise.all([axios.get('/a'), axios.get('/b')];

  // todo
}

so(request)().catch(e => {});

async/await

Promise配合Generator的超级语法糖,有点甜

/*多异步串行*/
async function request() {
  let r1 = await axios.get('/a');
  let r2 = await aixos.get('/b', { params: { id: r1.id } });

  // todo
}

request().catch(e => {});
/*多异步并行*/
async function request() {
  let [r1, r2] = await Promise.all([axios.get('/a'), axios.get('/b')];

  // todo
}

request().catch(e => {});