1. 异步编程
1.1. 内容概要
- 同步模式与异步模式
- 事件循环与消息队列
- 异步编程的几种方式
- Promise 异步方案,任务队列
- Generator 异步方案, async,await 语法糖
1.1.1. Promise
- 每一个 then 方法实际上都是为上一个 then 方法返回的 Promise 对象添加状态明确后的回调,then 的回调中可以手动返回 Promise 对象,如果 then 中的回调返回的是普通的值,会作为当前 then 方法返回的 Promise 的值 传入后一个 then 方法中的回调,如果没有返回值,返回 undefined
- then 方法返回的是全新的 Promise 对象
- 如果回调中返回的是 新的Promise 对象,后面的 then 方法回调会等待它的状态变更;也就是说后面的 then 方法是为前面返回的 Promise 对象注册了对应回调;但注意,此then方法回调中接收的值也就是新的Promise返回的值
- new Promise 实例化的时候,p1 的 resolve 函数可传入另外如 p2 的 promise 实例,resolve(p2).此时 p2 的状态决定了 p1 的状态,即使说 p2 会把状态传给 p1,如果 p2 状态为 fulfilled,p1 的 then 中回调函数会立即执行
- 当then方法传递一个普通类型的数据而不是回调函数的时候,此时链条中的值不会发生变化
如
Promise.resolve(1).then(2).then(console.log)
仍为1
1.1.2. 异常处理
- 在 then 方法第二个参数传入回调函数
- 通过 catch 方法
function ajax(url) {
return new Promsie((resolve, reject) => {
let 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();
});
}
ajax("/url").then(
function (value) {},
function (error) {
// 只是给第一个Promise(ajax)对象指定的异常回调
// 捕获不到上面then中的异常
}
);
ajax("/url")
.then(function (value) {}) // 返回了Promise对象
.catch(function (error) {
// 实际上是在给前面then方法返回的Promise对象指定失败回调,并不是给第一个Promise指定,只是因为promise链条上,异常会一直传递
});
1.1.3. Promise 静态方法
Promise.resolve()
快速的把一个值转换为 Promise 对象, 会把值包裹在Promise对象中,把创建出来的Promise对象作为Promise.resolve()的返回值
Promise.resolve("foo");
// 等价于
new Promise(function (resolve, reject) {
resolve("foo");
});
通过 Promise.resolve()包装一个 Promise 对象,得到的是相同的 Promise 对象
var p = ajax("/api/...");
p === Promise.resolve(p); // 如果是另外一个写法,不相等
1.1.4. 并行执行
Promise.all([])
只有多个 Promise 都成功结束才会成功结束,其中有返回失败结束,整体就会返回失败结束
Promise.race()
只会等待第一个任务结束
1.1.5. Promise 执行时序
Promise 的回调会作为微任务执行; 目前绝大多数异步调用都是作为宏任务执行,Promise, MutationObserver,node 中的 process.nextTick 作为微任务执行
1.2. Generator 异步方案
Promise 的缺点
仍然会有大量的回调函数,虽然没有嵌套,但是达不到传统同步代码形式的直观
funntion * foo () {
try {
const res = yield 'foo' // yield 关键字向外返回一个值, res === 'bar';暂停函数执行,直到下一次调用生成器对象的next方法再往下执行
console.log(res) // 如果next传入参数,会作为yield返回值
} catch (error) {
console.log(error)
}
}
const generator = foo()
const res = generator.next('bar') // { value: 'foo', done: false }
generator.throw(new Error('error'))
generator.throw 会对生成器函数内部抛出异常,内部能进行捕获
1.2.1. 实现异步编程
function* main() {
const users = yield ajax("/api/...");
const posts = yield ajax("/api/...");
}
const g = main();
const result = g.next(); // 返回上述第一个yield定义的Promise对象
result.value.then((data) => {
const result2 = g.next(data); // users
if (result2.done) return; // 思考: 此处可用递归
result2.value.then(
(data1) => {
g.next(data); // posts
},
(error) => {
g.throw(error); // 此时可以在main方法中通过try catch捕获异常
}
);
});
递归方式
function handleResult(result) {
if (result.done) return;
result.value.then((data) => {
handleResult(g.next(data));
});
}
对生成器函数的封装
function co(generator) {
const g = generator();
function handleResult(result) {
if (result.done) return;
result.value.then((data) => {
handleResult(g.next(data));
});
}
handleResult(g.next())
}
// https://github.com/tj/io
1.2.2. async/await
提供扁平化的异步编程体验,是生成器的语法糖,语言层面的的标准异步编程语法
- 把生成器函数修改为带有
async
关键字的函数,yield
关键字修改为await即可 - 调用async函数会返回Promise对象
async会把函数调用结果包装成为一个Promise,如
async funciton fn() {
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('time end')
resolve()
}, 2000)
})
}
console.log(fn) // Promise {<fulfilled>: undefined}
注意async可单独使用,如上,此时调用函数状态立即为fulfilled
使用await关键字后,包装后的Promise实例的状态根据await后的Promise resolve或reject而改变相应状态,如
async funciton fn() {
await new Promise((resolve, reject) => { // await..., return ... ,return await... 效果相同, 或者去掉async,直接return
setTimeout(() => {
console.log('time end')
resolve()
}, 2000)
})
}
console.log(fn) // Promise {<pending>: undefined}
var res = fn()
setTimeout(() => {
console.log(res) // Promise {<fuifilled>: undefined}
}, 3000)
想一想会很清晰,await后的Promise状态会传递给外层,联系到开头的第二点,回调中返回了另外一个Promise,后面的 then 方法回调会等待返回的这个Promise的状态变更,因此就能理解上面代码的效果
另外关于错误抛出的一个场景:
async mounted () {
this.chart.showLoading()
await this.getInfo()
console.log('hide')
this.chart.hideLoading()
}
async getInfo () {
await new Promise((resolve, reject) => {
setTimeout((resolve, reject) => {
reject('error')
},100)
}).then(res => {
console.log(res)
})
}
这里我们用定时器可以模拟网络操作,此时你会发现hide无法打印,后面的代码都不会执行.这是因为某个耗时操作中出了错,且我们没有捕获,解决这个问题很简单
要么then后面调用catch,即使不做任何处理,也要调用,或者try...catch包裹getInfo函数
因此,关于错误的捕获原则
- 永远要处理promise的rejected,监听unhandledRejection只是最后一道防线
- 处理stream流要处理错误
- 处理*sync,JSON.parse时,一定要用try...catch包裹,或者放入promise.且如果try...catch中还有try...catch, 则内层catch中的错误要throw出来被外层catch捕获