事件循环
意义:避免任务阻塞(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、5
第3轮 // "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化 参考:promisify、es6-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.iterator | Symbol.asyncIterator |
| next()返回值 | {value:…, done: true/false} | Promise({value:…, done: true/false}) |
| 迭代 | for...of | for 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等异步操作