今天把 MDN 的 JS 异步教程过完。
1. async 和 await
1. async 会把函数里的返回值用 Promise.resolve(result) 包裹起来(如果返回的本身就是 Promise 对象,就直接返回),但是整个函数不会变成异步函数,依旧是同步函数(除非你往 async 修饰的函数里再加异步过程(.then, new Promise等))。如果没有返回值,则返回 undefined,且该异步函数会和同步函数一样。可以说只有 return 值是异步的。
async function foo() {
console.log("1");
};
foo(); // 如果输出这个,是 undefined
console.log("2");
// 先输出1,再输出2。这完全就是同步嘛。
async function foo() {
console.log("1");
return 3; // 等同于 return Promise.resolve("3");
};
foo().then((result) => console.log(result)); // **别看漏了这一行**
console.log("2");
// 先输出1,再输出2,再输出3.
// async 修饰的函数,只有遇到 await 后才会开始异步,没有 await 的话最后的 return 也算异步
这很像 new Promise,不同的是 new Promise 在 new 的过程中就执行了,而 async 因为是个函数,所以要调用才能执行。
let a = new Promise((resolve) => {
console.log("1");
resolve("2");
});
// 只运行这个赋值语句会也输出 1(当然,只保留等号右边的也一样)
2. await 必须写在 async 里(如果在 js 里写会报错,但是在开发者工具里写不会),等待 await 后面的表达式(Promise 或者值)。await 会解包这个表达式返回的值。
async function foo() {
let p = new Promise((resolve) => {
setTimeout(() => resolve("1"), 1000);
});
console.log(p) // 输出 Promise{<pending>}
console.log(await p) // 1 秒后,输出 1,而不是 Promise{<fulfilled>: "1"}
console.log(p) // 上一步输出完毕后,输出 Promise{<fulfilled>: "1"}
}
foo();
碰到 await 后,async 函数就会立即变成异步函数,暂停执行,无论 await 后面是否是已经解决好的值。
执行到任务队列后:
- 如果 await 后面的是非 Promise,就直接返回那个变量。
- 如果 await 后面的 Promise 被拒绝了,则包含该 await 的 async 函数终止运行,返回拒绝状态的 Promise 对象,后面的代码不执行。(也就是提前终止)
- 如果 await 后面的 Promise 没有被拒绝,则继续运行后面的代码,直到遇到 return。
let a = await 3; // 相当于 let a = 3;
async function foo(){
console.log("1");
await new Promise((resolve, reject) => {
reject("你错了");
}); // 直接返回拒绝状态的 Promise 对象,后面两行其实不运行
console.log("2"); // 不执行
return 1; // 不执行
};
b = foo();
console.log(b); // 输出 Promise{<pending>}
// 整体输出顺序
// 1
// Promise{<pending>}
// 报错
(注:如果不加 await,只是在 async 函数里产生了一个 rejected 的 Promise对象,该 async 函数不会捕获该错误,不管是 .catch() 还是try...catch...,都要在前面加 await 才能被捕获)
async function foo() {
try{
Promise.reject("你错了");
console.log("我当然错了"); // 这次这个会被执行,因为没有 await 了。
} catch (e) {
console.log(e);
}
}
foo(); // 报错然后输出 “我当然错了”, try...catch... 无法捕获该错误,换成 foo().catch() 也一样
async function foo() {
try{
await Promise.reject("你错了");
console.log("我当然错了"); // 有 await 后,这个就不执行了(当然,await 要出错才行)
} catch (e) {
console.log(e);
}
}
foo(); // “你错了” 这个错误被捕获,因为有 await,这回“我当然错了”不被打印了
下面是一个小测试,猜猜输出什么
async function foo() {
try {
await Promise.resolve("你错了");
} catch (e) {
console.log(e);
}
}
foo().then(console.log);
答案:输出的是 undefined。
3. await Promise.all([a,b,c]) 可以用于等待多个 Promise。
2. 所有异步方法总结
1. 回调函数
XHR ,以及 setXXX 系列,还有 requestAnimationFrame。
如果代码块有可能运行时间会长于设定的时间的话,用 setTimeout 递归。
如果是动画,一般选择 requestAnimationFrame,除非对速率有特别的要求。
2. Promise
现代异步方法,使用 fetch API 代替 XHR。
还可以自定义 new Promise((resolve, reject) => {}), 括号里面的函数会同步运行,直到 resolve 或者 reject 调用。
3. async & await
await 必须在 async 函数内调用
如果没有 await,async 函数就和普通函数完全一样。
缺点是浏览器支持问题,不过大多数现代浏览器都支持。如果要支持旧浏览器,可以用 babel.js,这个转码器可以将很多现代语法转成传统语法。