JavaScript 异步编程
同步模式与异步模式
同步模式下代码的执行顺序
console.log('global begin'); // 1. 将此行压入调用栈执行,输出到控制台
function bar() {
console.log('bar task'); // 5. 将此行压入调用栈执行,输出到控制台
}// 6. 将 bar() 函数从调用栈弹出
function foo() {
console.log('foo task'); // 3. 将此行压入调用栈执行,输出到控制台
bar(); // 4. 将 bar 压入调用栈执行 bar() 中的代码
}// 7. 将 foo() 函数从调用栈弹出
foo(); // 2. 将 foo 压入调用栈执行 foo() 中的代码
console.log('global end'); // 8. 将此行压入调用栈执行,输出到控制台
异步模式
执行过程
- 为了解决同时处理大量耗时任务的问题
- 图片解析 async 方法中代码执行过程
console.log('global begin');
setTimeout(function timmer1() {
console.log('timer1 invoke');
}, 1800);
setTimeout(function timer2() {
console.log('timer2 invoke');
setTimeout(function inner() {
console.log('inner invoke');
}, 1000);
}, 1000);
console.log('global end');
- 将整块代码压入调用栈
- 将 console.log(‘global begin’); 压入调用栈输出控制台并立即弹出
- 调用栈将 timer1 压入,通知 WebAPI 独立于当前线程开启计时器,并立即弹出调用栈
- 调用栈将 timmer2 压入,WebAPI 开启计时器 timmer2,调用栈立即弹出 timmer2
- 调用栈将 console.log(‘global end’); 压入,输出控制台并立即弹出,接着将整个 async 代码片段弹出
- 此时调用栈被清空,事件循环侦听消息队列中并无待办项
- 倒计时执行条件满足后依次执行:timmer 2 中的代码被推入消息队列,事件循环侦听到消息队列中有代办项,并且调用栈为空,将 timmer 2 压入调用栈;将 console.log(‘timmer2 invoke’) 压入调用栈输出到控制台并且立即弹出
- 调用栈将 setTimOut(inner) 推入栈中,通知 WebAPI 开启计时器 inner,并依次将 setTimout(inner) 和 timmer2() 弹出,整个过程完成后消息队列和调用栈均为空
- timmer1 倒计时结束率先触发,消息队列将 timmer1 中的语句入列,此时事件循环立即通知调用栈将 timmer1 取走执行,完成之后消息队列和调用栈依次被清空
10.inner 倒计时结束,消息队列将 inner 中的语句入列,此时事件循环立即通知调用栈取走执行,输出到控制台语句分别入栈、执行、出栈后整个过程结束
开启一个异步线程执行流程图
异步编程的几种方式
Promise 概述
回调函数
回调函数可以理解为一件你想要做的事情
CommomJS 提出 Promise 规范
Promise 常见误区
- 不采用链式调用也会造成回调地狱
Promise 链式调用
- then 方法的回调函数返回的 promise 对象是另外一个新的实例
- 后面的 then 方法就是在为上一个 then 返回的 Promise 注册回调
- 前面 then 方法中回调函数的返回值会作为后面 then 方法回调的参数
- 如果回调中返回的是 promise,那么后面的方法会等待返回再继续执行
Promise 异常处理
- 推荐在链式调用的末尾统一处理
Promise 静态方法
- Promise.resove
- Promise.reject
Promise 并行执行
- Promise.all
- Promise.race
Promise 执行时序
- es6 当中微任务概念的引入
- Promise 的回调函数会作为微任务率先执行
function log(...args) {
console.log(args.join(', '));
}
log('global start');
setTimeout(() => {
log('timeout'); // 等待 promise 微任务执行完成之后最后执行
}, 0);
// 微任务:在当前任务结束过后立即执行的任务
Promise.resolve()
.then(() => {
log('p1');
})
.then(() => {
log('p2');
})
.then(() => {
log('p3');
});
log('global end');
Generator 的使用
generator 异步调用
function log(...args) {
console.log(args.join(', '));
}
function* foo() {
log('generator foo');
try {
const res = yield 'foo';
log(res);
} catch (e) {
console.log(e);
}
}
const generator = foo();
const res1 = generator.next();
log(JSON.stringify(res1));
// 1. 传参入 foo
// const res2 = generator.next('scope');
// log(JSON.stringify(res2));
// 2. 抛出异常给 foo
// const res2 = generator.throw(new Error('generator error'));
// log(JSON.stringify(res2));
// 3. 返回值
const res2 = generator.return('return');
log(JSON.stringify(res2));
generator 典型用例
function ajax(url) {
return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
xhr.send();
});
}
function* model() {
const { data: user } = yield ajax('http://localhost:3001/api/userData');
console.log(user);
const { data: post } = yield ajax('http://localhost:3001/api/postData');
console.log(post);
}
const generator = model();
const result = generator.next();
const { value: userAction } = result;
userAction.then(user => {
const { value: postAction } = generator.next(user);
postAction.then(post => {
generator.next(post);
});
});
带来的问题,重复调用有复杂的代码
generator 用例优化
function ajax(url) {
return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function () {
switch (this.status) {
case 200:
resolve(this.response);
break;
case 404:
reject(new Error('找不到的异常'));
break;
default:
reject(new Error(this.statusText));
break;
}
};
xhr.send();
});
}
function* model() {
const { data: user } = yield ajax('http://localhost:3001/api/userData');
console.log(user);
const { data: post } = yield ajax('http://localhost:3001/api/postData');
console.log(post);
const err = yield ajax('/api/errData');
console.log(err);
}
function co(generator, params) {
const step = generator.next(params);
const { value, done } = step;
if (done) return;
value.then(
data => {
co(generator, data);
},
e => {
generator.throw(e);
},
);
}
Async Await 方案
function ajax(url) {
return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function () {
switch (this.status) {
case 200:
resolve(this.response);
break;
case 404:
reject(new Error('找不到的异常'));
break;
default:
reject(new Error(this.statusText));
break;
}
};
xhr.send();
});
}
async function main() {
const { data: user } = await ajax('http://localhost:3001/api/userData');
console.log(user);
const { data: post } = await ajax('http://localhost:3001/api/postData');
console.log(post);
const err = await ajax('/api/errData');
console.log(err);
}
main(); // 调用时省略了 co 函数