异步编程

95 阅读5分钟

事件循环

意义:避免任务阻塞(js为单线程),js主线程执行同步任务,执行完成后从任务队列获取、执行异步任务(包含宏任务、微任务)

  • 宏任务: setTimeout、setImmdiate、同步代码、ajax
  • 微任务:promise处理程序, await, process.nextTick, queueMicrotask
async function async1() {
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 end')
}
async1()

setTimeout(function () {
    console.log('setTimeout1')
}, 1000)  // 1s后加入宏任务队列

new Promise(resolve => {
    console.log('Promise')
    resolve()
})
.then(function () {
    console.log('promise1')
})
.then(function () {
    setTimeout(function () {
        console.log('setTimeout2');
        process.nextTick(function() {
            console.log('3');
        })
        new Promise(function(resolve) {
            console.log('4');
            resolve();
        }).then(function() {
            console.log('5')
        })
    })
});
console.log('script end')

第1// "async2 end"、"Promise"、"script end"、"async1 end"、"promise1"2// "setTimeout2"、4、3、53// "setTimeout1"

(1) js主线程执行同步代码,耗时任务放入相应的宏任务/微任务队列(宏任务是到时间了才会放在宏任务队列, 微任务是立刻放入到微任务队列)

(2) 每次开始新的宏任务执行前先执行所有的微任务

(3) 一轮事件循环包含相应的宏任务和微任务

Promise使用场景

  • 链式调用,并行,时序
  • 包装异步操作, 使其拥有promise相关功能
  • 包装对象(Promise.resolve(), Promise.reject())
  • promise 化(将基于回调的函数和库 promisify)

1. promise链式调用与异常捕获

new Promise((resolve, reject) => {
  resolve('success');
  console.log('a complete'); 
})
.then((resp1) => {
  console.log(resp1);
})
.catch(() => {
  console.log('capture failure');
})
.then(() => {
  // 继续执行新的操作
});
setTimeout(() => { console.log(123) }, 1000);
// a complete
// success
// 123

// 链式调用总结:
// 1. 总是返回或者终止promise链, 并行操作(例如Promise.all)时,尽量为每个异步操作定义单独的异常处理回调
// 2. 扁平化链式调用
// 3. 回调函数返回promise时,会替换掉原有的由处理程序生成的promise对象
// 4. finally常用于在处理结果/错误之前终止加载指示器

2. promise拒绝事件

用于调试promise相关问题, 例如当 Promise失败时不要执行默认操作(默认操作一般会包含把错误打印到控制台)。

window.addEventListener("unhandledrejection", event => {
  /* 你可以在这里添加一些代码,以便检查
     event.promise 中的 promise 和
     event.reason 中的 rejection 原因 */

  event.preventDefault();
}, false);

3. 组合

(1) 并行

Promise.all, Promise.race, promise.any, promise.allSettled

Promise.all异步性:对于空数组,可视为同步操作,即立即返回一个resolved状态的promise

(2) 时序

[func1, func2, func3].reduce((p, f) => p.then(f), Promise.resolve())
.then(results => { /* use results */ });

// 相当于Promise链
Promise.resolve().then(func1).then(func2).then(func3);

4. Promises/A+规范

参考: Promises/A+

一个简单的案例,处理程序返回一个thenable对象,确保promise的继续链式调用(thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可,thenable特性允许自定义对象与promise链进行集成)

5. Promisification

将基于回调的函数或库promise化 参考:promisifyes6-promise

// 通用的辅助函数
function promisify(f, flag) {
  // 返回一个 f 的包装器。该包装器返回一个 promise,并将调用转发给原始的 f
  return function (...args) { 
    return new Promise((resolve, reject) => {
      function callback(err, ...results) {
        if (err) {
          reject(err);
        } else {
          resolve(flag ? results : results[0]);
        }
      }

      args.push(callback);

      f.call(this, ...args);
    });
  };
}

// 用法:
let loadScriptPromise = promisify(loadScript, true);
loadScriptPromise(...).then(...);

6. Promise实现原理

// 简易实现
function PromiseFun(fn) {
  this.cbs = [];

  const resolve = (value) => {
    setTimeout(() => {
      this.data = value;
      this.cbs.forEach((cb) => cb());
    });
  };
  // reject...
  fn(resolve);
}

PromiseFun.prototype.then = function (onResolved) {
  return new PromiseFun((resolve) => {
    this.cbs.push(() => {
      const res = onResolved(this.data);
      if (res instanceof PromiseFun) {
        res.then(resolve);
      } else {
        resolve(res);
      }
    });
  });
};

// 总结:
1. new Promise(executor) executor传入时立即执行,并在其中定义回调;
2. 执行一些操作后,调用定义的回调并传入相关参数;
3. 参数值value被返回给绑定的promise对象中;
4. 该promise调用`.then(handleResolved)` 接收 `value` 作为入参被传递给了 `handleResolved` 回调

7. 优雅中断promise

AbortController, AbortSignal

function myCancelablePromise ({ signal }) {
    return new Promise((resolve, reject) => {
        // signal存在并且是终止的状态,直接抛出异常
        if (signal) {
          signal.throwIfAborted();  // 等价于signal.reason
        }

        setTimeout(() => {
            Math.random() > 0.5 ? resolve('ok') : reject(new Error('not good'));
        }, 1000);

        // 监听abort事件
        if (signal) {
          signal.addEventListener('abort', () => reject(signal.reason)); 
        }
    });
}

const ac = new AbortController();
const abortSignal = ac.signal;

myCancelablePromise({ signal: abortSignal }).then((res) => console.log(res), err => console.warn(err));  // signal is aborted without reason
ac.abort();


// const abortSignal = AbortSignal.timeout(100);  // 100ms后终止的promise对象
// myCancelablePromise({ signal: abortSignal }).then((res) => console.log(res), err => console.warn(err));  // DOMException: signal timed out

中断fetch请求: AbortController; 中断axios: AbortController、CancelToken

8. 常用解决方案比较

  • async/await

es module中可以使用顶层await

// async错误捕获
async function f() { throw new Error("xxx!"); }
// 等价于
async function f() { 
    try { 
        let response = await fetch('xxx');
    } catch(err) { 
        alert(err);
    }
}
f();
// 或
async function f() { 
    let response = await fetch('xxx');
}
f().catch();
  • generator: 可迭代

(1)generator 是通过 generator 函数 function* f(…) {…} 创建的

(2)在 generator(仅在)内部,存在 yield 操作

(3)外部代码和 generator 可能会通过 next/yield 调用交换结果

// 使用generator创建迭代器生成函数
const iterable1 = {
    [Symbol.iterator]: function* () {  // for..of 循环开始时被调用
      yield 1;
      yield 2;
    };
};
[...iterable1]; // [1, 2]

g.return('foo'); // 停止执行并返回给定值value, 用于特定情况下中断generator
g.throw(error); // 向yield抛出错误

异步迭代

迭代器 vs 异步迭代器

Iterator异步Iterator
迭代器生成函数Symbol.iteratorSymbol.asyncIterator
next()返回值{value:…, done: true/false}Promise({value:…, done: true/false})
迭代for...offor await ... of(异步迭代)

generator vs 异步generator

generator异步generator
声明function*async function*
next()返回值{value:…, done: true/false}Promise({value:…, done: true/false})

异步generator可用于分段处理大型数据流

---------------------------------------不同点比较-------------------------------------------

  • promise错误使用.then(succb, errcb)或catch捕获,async/generator使用try/catch捕获
  • Promise一旦新建就会立即执行,不会阻塞后面的代码,而async函数中await会阻塞后面的代码
  • async函数会隐式地返回一个promise,代码更简洁(如同同步代码)避免了嵌套代码;await后的语句相当于Promise的then
  • generator函数返回一个迭代器,generator函数体内外数据交换机制,操作需要暂停的地方使用yield语句注明

参考链接: 优雅中断Promise等异步操作