前人栽树
许大仙 aexiar.github.io
前言
函数对象和实例对象
- 函数对象:将函数作为对象使用,简称为函数对象。
- 实例对象:new 构造函数或类产生的对象,称为实例对象。
示例:
<template>
</template><script>
export default {name: "promise-01",data() {return {}},created() {// 此时 的 Person 就是函数对象,其实就和 ES6 中的静态属性和静态方法原理上是一样的。this.Person.country = '中国';console.log(this.Person.country); // 中国// 通过 new 构造函数产生的对象 p,称为实例对象const p = new this.Person('许大仙', 18);console.log(p); // Person {name: '许大仙', age: 18}},methods: {Person(name, age) {this.name = name;this.age = age;}}
}
</script><style scoped></style>
回调函数
回调函数的定义
-
如果将函数 A 作为参数传递给函数 B 时,我们就称函数 A 为
回调函数
。换言之,如果一个函数定义了,我们没有调用,但是最终却执行了,那么这个函数就可以称为回调函数。 -
回调函数常用于 JS 中的事件、定时器、数组等。
-
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><button id="btn">点我</button><script>// 如果将函数 A 作为参数传递给函数 B 时,我们就称函数 A 为 `回调函数` 。换言之,如果一个函数定义了,我们没有调用,但是最终却执行了,那么这个函数就可以称为回调函数。let btn = document.querySelector('#btn');btn.addEventListener('click', function () {// 回调函数console.log('大胆,谁让你点我的~');});</script>
</body>
</html>
- 示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><button id="btn">点我</button><script>// 如果将函数 A 作为参数传递给函数 B 时,我们就称函数 A 为 `回调函数` 。换言之,如果一个函数定义了,我们没有调用,但是最终却执行了,那么这个函数就可以称为回调函数。let btn = document.querySelector('#btn');// 这边很多人会有疑惑,为什么直接写 fn ,而不是 fn() ?// 如果写 fn() ,那么就会立即执行函数,其次,addEventListener 函数的定义就像下面的伪代码:// function addEventListener(event,callback){// callback();// }btn.addEventListener('click', fn);function fn() {// 回调函数console.log('大胆,谁让你点我的~');}</script>
</body>
</html>
同步的回调函数
-
同步的回调函数,会立即在主线程上执行,不会放入到回调队列中。
-
例子:数组遍历相关的回调函数、Promise 的 executor 函数。
-
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>let arr = [1, 3, 5, 7, 9];arr.forEach(value => {// 同步回调函数console.log(value);});console.log('主线程的代码');</script>
</body>
</html>
异步的回调函数
-
异步的回调函数,不会立即执行,会放入回调队列中再执行。
-
例子:定时器回调、ajax 回调、Promise 成功或失败的回调。
-
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>setTimeout(() => {// 异步回调函数console.log('定时器执行了');}, 0);console.log('主线程的代码');</script>
</body>
</html>
JavaScript 的异常处理
-
JavaScript 中的异常(错误)类型:
- Error:所有异常(错误的父类型。
- ReferenceError:引用的变量不存在。
- TypeError:数据类型不正确的错误。
- RangeError:数据值不在其所允许的范围内。
- SyntaxError:语法错误。
-
JavaScript 中的异常处理:
- 捕获异常:try … catch。
- 抛出异常:throw error。
-
error 对象的结构:
- message 属性:错误相关信息。
- stack 属性:记录信息。
-
示例:
<template>
</template><script>
export default {name: "promise-01",data() {return {}},created() {// 捕获异常try {let result = this.divide(10, 0);console.log(result);} catch (e) {console.error(e);}},methods: {divide(num1, num2) {if (num2 === 0) {// 抛出自定义异常throw new Error('被除数不能为 0 ');}return num1 / num2;},}
}
</script><style scoped></style>
异步代码产生的问题
- 回调地狱
Promise 概述
- Promise 的理解
- 抽象表达:Promise 是 JavaScript 进行异步编程的新方案(旧方案是纯回调函数,纯回调函数会产生回调地狱的问题)。
- 具体表达:
- 从语法上讲,Promise 是一个构造函数。
- 从功能上讲,Promise 对象是用来封装一个异步操作并可以获取其结果。
- Promise 的状态
- ① pending(初始化状态) 变为 fulfilled(成功状态) 。
- ② pending(初始化状态) 变为 rejected(失败状态) 。
- Promise 是一个类(构造函数),英文翻译承诺、许诺。Promise 是 ES6+ 中引入的,用于解决回调地狱和异步操作的方案。
- Promise 通过提供一种结构化的方式来处理这些问题。它可以被看作是一个承诺,代表着一个异步操作的最终结果(可以是成功的值或失败的原因)。
注意:
- Promise 只有这两种状态,且一个 Promise 对象只能被改变一次。
- 无论变为成功或失败,都会有一个结果数据。
- 成功的结果数据一般称为 value ,失败的结果数据一般称为 reason (行业规范)。
- 示例
created() {
/*
* ① Promise 不是一个回调函数,是一个内置的构造函数,是程序员自己 new 调用的。
* ② new Promise((resolve, reject) => {}) 的时候要传入一个回调函数,这个回调函数是同步的,会立即在主线程上执行。这个回调函数称为 executor 函数。
* ③ 每一个 Promise 实例都有 3 种状态,分别为初始化(pending)、成功(fulfilled)和失败(rejected)。
* ④ 每一个 Promise 实例在刚被 new 出来的那一刻的状态都是初始化(pending)。
* ⑤ executor 函数会接收到 2 个函数,它们都是函数,分别用形参:resolve 和 reject 接收。
* 1)调用 resolve 会让 Promise 实例的状态变为成功(fulfilled),同时可以指定成功的 value。
* 2)调用 reject 会让 Promise 实例的状态变为失败(rejected),同时可以指定失败的 reason 。
*/// 创建一个 Promise 实例对象const promise = new Promise((resolve, reject) => {console.log('11');resolve(11);});console.log(promise);},
Promise 基本使用
Promise 的重要语法
new Promise(executor)
Promise.prototype.then
基本编码流程
- 创建 Promise 的实例对象(pending状态),传入 executor 函数。
- 在 executor 中启动异步任务(定时器、Ajax 请求)。
- 根据异步任务的结果,做不同的结果:
- 如果异步任务成功了,就调用 resolve(value) ,将 Promise 实例的状态变为成功(fulfilled),同时指定成功的 value 。
- 如果异步任务失败了,就调用 reject(reason),将 Promise 实例的状态变为失败(rejected),同时指定失败的 reason 。
- 通过 then 方法为 Promise 实例指定成功、失败的回调函数,来获取成功的 value 和失败的 reason 。
注意:then 方法里面指定的成功和失败的回调函数,都是异步的回调函数。
关于状态的注意点
- 三个状态:
- pending:未确定的 — 初始化状态。
- fulfilled:成功的 — 调用 resolve() 后的状态。
- rejected:失败的 — 调用 reject() 后的状态。
- 两种状态改变:
- 状态只能修改一次!!
- 一个 Promise 实例指定了多个成功/失败的回调函数,都会被调用。
示例代码:
/** 1. 重要语法* ① new Promise(executor)构造函数* ② Promise.prototype.then 方法* 2. 基本编码流程* ① 创建 Promise 的实例对象(pending状态),传入 executor 函数。* ② 在 executor 中启动异步任务(定时器、Ajax 请求)。* ③ 根据异步任务的结果,做不同的结果:* 1)如果异步任务成功了,就调用 resolve(value) ,将 Promise 实例的状态变为成功(fulfilled),同时指定成功的 value 。* 2)如果异步任务失败了,就调用 reject(reason),将 Promise 实例的状态变为失败(rejected),同时指定失败的 reason 。* ④ 通过 then 方法为 Promise 实例指定成功、失败的回调函数,来获取成功的 value 和失败的 reason 。* 注意:then 方法里面指定的成功和失败的回调函数,都是异步的回调函数。* 3. 关于状态的注意点* ① 三个状态:* pending:未确定的 --- 初始化状态* fulfilled:成功的 --- 调用 resolve() 后的状态* rejected:失败的 --- 调用 reject() 后的状态* ② 两种状态改变:* pending --> fulfilled* pending --> rejected* ③ 状态只能修改一次!!* ④ 一个 Promise 实例指定了多个成功/失败的回调函数,都会调用。*/// 创建一个 Promise 实例对象const promise = new Promise((resolve, reject) => {// 启动异步任务setTimeout(() => {let flag = true;let data = '用户数据';if (flag) {// 异步任务成功,就调用 resolve(value)resolve(data);} else {// 异步任务失败,就调用 reject(reason)reject(data);}}, 1000);});promise.then(value => {// 成功的回调--异步console.log('成功的回调1', value);}, reason => {// 失败的回调--异步console.error('失败的回调1', reason);});promise.then(value => {// 成功的回调--异步console.log('成功的回调2', value);}, reason => {// 失败的回调--异步console.error('失败的回调2', reason);});
Promise API
Promise 的构造函数
- 语法:
new Promise(executor){}
- executor 函数是同步执行的,
(resolve,reject) => {}
。 - resolve 函数:调用 resolve 会将 Promise 的实例内部的状态改为成功(fulfilled)。
- reject 函数:调用 rejected 会将 Promise 的实例内部的状态改为失败(rejected)。
注意:executor 函数会在 Promise 内部立即同步调用,异步代码需要放在 executor 函数中的函数体位置。
Promise.prototype.then 方法
- 语法:
const promise = new Promise((resolve,reject) =>{});
promise.then(onFulfilled,onRejected);
- onFulfilled:成功的回调函数,
(value) =>{}
。 - onRejected:成功的回调函数,
(reason) =>{}
。
注意:then 方法会返回一个新的 Promise 实例对象。那么就形成了链式编程。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/* 注意:then 方法会返回一个新的 Promise 实例对象。 */const promise = new Promise((resolve, reject) => {setTimeout(() => {resolve(100);}, 1000);});let p = promise.then(value => {console.log(value);}, reason => {console.log(reason);});console.log(p);console.log(promise === p); // falseconsole.log(p instanceof Promise); // true</script>
</body>
</html>
Promise.prototype.catch 方法
- 语法:
const promise = new Promise((resolve,reject) =>{});
promise.catch(onRejected);
- onRejected:失败的回调函数,
(reason) =>{}
。
注意:catch 方法是 then 方法的语法糖,相当于 then(onfulfilled,onRejected)。
示例:
<script>/* Promise.prototype.catch 方法 */const promise = new Promise((resolve, reject) => {setTimeout(() => {reject(-100);}, 1000);
});promise.then(value => {console.log("value", value);
}, reason => {console.log("reason", reason);
})promise.catch(reason => {console.log(reason);
});
</script>
Promise.resolve 方法
- 语法:
Promise.resolve(value);
- 用于快速返回一个状态为 fulfilled 或 rejected 的 Promise 实例。
注意:value 的值可能是 非Promise 的值(返回成功)或 Promise 的值(如果是失败的 Promise ,返回失败)。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/* Promise.resolve 方法 */const p = Promise.resolve(100);p.then(value => {// 100console.log(value);}, reason => {});</script>
</body>
</html>
输出 100
Promise.reject 方法
- 语法:
Promise.reject(reason);
- 用于快速返回一个状态必为 rejected 的 Promise 实例。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/* Promise.reject 方法 */const p = Promise.reject(100);p.then(value => {console.log(value);}, reason => {// 100console.error(reason);});console.log(p)</script>
</body>
</html>
Promise.all 方法
- 语法:
Promise.all(promiseArr)
- promiseArr:包含 n 个 Promise 实例的数组。
- 返回一个新的 Promise 实例,只有所有的 Promise 实例成功才成功,只要有一个失败就直接失败。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/* Promise.all 方法 */const p1 = Promise.resolve(1);const p2 = Promise.resolve(2);const p3 = Promise.resolve(3);const p = Promise.all([p1, p2, p3]);p.then(value => {// [1, 2, 3]console.log('value', value);}, reason => {console.log('reason', reason);});</script>
</body>
</html>
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/* Promise.all 方法 */const p1 = Promise.resolve(1);const p2 = Promise.reject(-2);const p3 = Promise.resolve(3);const p = Promise.all([p1, p2, p3]);p.then(value => {console.log('value', value);}, reason => {// reason -2console.log('reason', reason);});</script>
</body>
</html>
Promise.race 方法
- 语法:
Promise.race(promiseArr)
- promiseArr:包含 n 个 Promise 实例的数组。
- 返回一个新的 Promise 实例,以最先出结果的 Promise 实例为准。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/* Promise.race 方法 */const p1 = new Promise((resolve, reject) => {setTimeout(() => {resolve(222);}, 3000);});const p2 = new Promise((resolve, reject) => {setTimeout(() => {reject(222);}, 2000);});const p3 = new Promise((resolve, reject) => {setTimeout(() => {reject(333);}, 1000);});const p = Promise.race([p1, p2, p3]);p.then(value => {console.log('value', value);}, reason => {// reason 333console.log('reason', reason);});</script>
</body>
</html>
使用注意事项
如何改变一个 Promise 实例的状态?
① 执行 resolve(value) :如果当前是 pending ,就会变为 fulfilled 。
② 执行 reject(reason):如果当前是 pending ,就会变为 fulfilled 。
③ 执行器函数(executor)抛出异常:如果当前是 pending,就会变为 rejected 。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/** 如何改变一个 Promise 实例的状态?* ① 执行 resolve(value) :如果当前是 pending ,就会变为 fulfilled 。* ② 执行 reject(reason):如果当前是 pending ,就会变为 fulfilled 。* ③ 执行器函数(executor)抛出异常:如果当前是 pending,就会变为 rejected 。*/const p = new Promise((resolve, reject) => {// 函数体throw new Error('出错啦');});p.then(value => {console.log(`value = ${value}`);}, reason => {// reason = Error: 出错啦console.log(`reason = ${reason}`);});</script>
</body>
</html>
改变 Promise 实例的状态和指定回调函数谁先谁后?
① 都有可能,正常情况下是先指定回调再改变状态,但是也可以先改变状态再指定回调?
② 如何先改状态再指定回调?只需要延迟一会再调用 then() 方法。
③ Promise 实例什么时候能得到数据?
- 如果先指定的回调,那么当状态发生改变的时候,回调函数就会调用,得到数据。
- 如果先改变的状态,那么当指定回调的时候,回调函数就会调用,得到数据。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/** 改变 Promise 实例的状态和指定回调函数谁先谁后?* ① 都有可能,正常情况下是先指定回调再改变状态,但是也可以先改变状态再指定回调。* ② 如何先改状态再指定回调?只需要延迟一会再调用 then() 方法。* ③ Promise 实例什么时候能得到数据?* 如果先指定的回调,那么当状态发生改变的时候,回调函数就会调用,得到数据。* 如果先改变的状态,那么当指定回调的时候,回调函数就会调用,得到数据。*/// 正常情况下是先指定回调再改变状态const p = new Promise((resolve, reject) => {// 函数体setTimeout(() => {resolve(11);}, 1000);});p.then(value => {// 函数体console.log('成功了', value);}, reason => {// 函数体console.log('失败了', reason);});</script>
</body>
</html>
- 示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/** 改变 Promise 实例的状态和指定回调函数谁先谁后?* ① 都有可能,正常情况下是先指定回调再改变状态,但是也可以先改变状态再指定回调。* ② 如何先改状态再指定回调?只需要延迟一会再调用 then() 方法。* ③ Promise 实例什么时候能得到数据?* 如果先指定的回调,那么当状态发生改变的时候,回调函数就会调用,得到数据。* 如果先改变的状态,那么当指定回调的时候,回调函数就会调用,得到数据。*/// 先改变状态再指定回调const p = new Promise((resolve, reject) => {// 函数体resolve(11);});setTimeout(() => {p.then(value => {// 函数体console.log('成功了', value);}, reason => {// 函数体console.log('失败了', reason);});}, 2000);</script>
</body>
</html>
then 的链式调用
- 『问』Promise实例.then() 返回的是一个
新 Promise 实例
,它和值和状态由什么决定? - 『答』:
- 简单表达:由 then() 所指定的回调函数执行的结果决定的。
- 详细表达:
- 如果 then 所指定的回调返回的是
非Promise 值 a
,那么新 Promise 实例
状态为成功(fulfilled),成功的 value 为 a。 - 如果 then 所指定的回调返回的是一个
Promise 实例 p
,那么新 Promise 实例
的状态和值都和 p 一致。 - 如果 then 所指定的回调
抛出异常
,那么新Promise实例
状态为 rejected ,reason 为抛出的异常。
- 如果 then 所指定的回调返回的是
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/**『问』Promise实例.then() 返回的是一个 `新 Promise 实例` ,它和值和状态由什么决定?*『答』:* 简单表达:由 then() 所指定的回调函数执行的结果决定的。* 详细表达:* 如果 then 所指定的回调返回的是 `非Promise 值 a` ,那么`新 Promise 实例`状态为成功(fulfilled),成功的 value 为 a。* 如果 then 所指定的回调返回的是一个 `Promise 实例 p` ,那么`新 Promise 实例`的状态和值都和 p 一致。* 如果 then 所指定的回调`抛出异常`,那么`新Promise实例`状态为 rejected ,reason 为抛出的异常。*/const p = new Promise((resolve, reject) => {// 函数体setTimeout(() => {resolve('a');}, 1000);});const x = p.then(value => {// 函数体console.log('成功1', value); // 成功1 a}, reason => {// 函数体console.log('失败1', reason);});x.then(value => {// 函数体console.log('成功2', value); // 成功2 undefined}, reason => {// 函数体console.log('失败2', reason);});</script>
</body>
</html>
- 示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>/**『问』Promise实例.then() 返回的是一个 `新 Promise 实例` ,它和值和状态由什么决定?*『答』:* ① 简单表达:由 then() 所指定的回调函数执行的结果决定的。* ② 详细表达:* 如果 then 所指定的回调返回的是 `非Promise 值 a` ,那么`新 Promise 实例`状态为成功(fulfilled),成功的 value 为 a。* 如果 then 所指定的回调返回的是一个 `Promise 实例 p` ,那么`新 Promise 实例`的状态和值都和 p 一致。* 如果 then 所指定的回调`抛出异常`,那么`新Promise实例`状态为 rejected ,reason 为抛出的异常。*/const p = new Promise((resolve, reject) => {// 函数体setTimeout(() => {resolve('a');}, 1000);});// 链式调用p.then(value => {// 函数体console.log('成功1', value); // 成功1 a}, reason => {// 函数体console.log('失败1', reason);}).then(value => {// 函数体console.log('成功2', value); // 成功2 undefined}, reason => {// 函数体console.log('失败2', reason);});</script>
</body>
</html>
纯回调引起的回调地狱问题
- 纯回调会引起回调地狱问题。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>// 用户数据 ==> 订单数据 ==> 商品数据setTimeout(() => {console.log('用户数据');setTimeout(() => {console.log('订单数据');setTimeout(() => {console.log('商品数据');}, 1000);}, 1000);}, 1000);</script>
</body>
</html>
使用 Promise 实例的 then() 方法解决回调地狱问题
- Promise 实例的 then() 的链式调用可以解决回调地狱问题。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>// 用户数据 ==> 订单数据 ==> 商品数据const p = new Promise((resolve, reject) => {setTimeout(() => {resolve('用户数据');}, 1000);});p.then(value => {console.log(value);return new Promise((resolve, reject) => {setTimeout(() => {resolve('订单数据');}, 1000);});}, reason => {console.error(reason);}).then(value => {console.log(value);return new Promise((resolve, reject) => {setTimeout(() => {resolve('商品数据');}, 1000);});}, reason => {console.error(reason);}).then(value => {console.log(value);}, reason => {console.error(reason);});</script>
</body>
</html>
中断 Promise 链
- 『问』当使用 Promise 实例的 then 链式调用时,需要在中间中断,不再调用后面的回调函数?
- 『答』在回调函数中返回一个 pending 状态的 Promise 对象。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>// 只要有一个请求出现问题,就不再发送请求:用户数据 ==> 订单数据 ==> 商品数据// 当使用 Promise 实例的 then 链式调用时,需要在中间中断,不再调用后面的回调函数?// 在回调函数中返回一个 pending 状态的 Promise 对象。const p = new Promise((resolve, reject) => {setTimeout(() => {reject('用户数据');}, 1000);});p.then(value => {console.log('第一次请求成功', value);return new Promise((resolve, reject) => {setTimeout(() => {resolve('订单数据');}, 1000);});}, reason => {console.error('第一次请求失败', reason);return new Promise(() => {});}).then(value => {console.log('第二次请求成功', value);return new Promise((resolve, reject) => {setTimeout(() => {resolve('商品数据');}, 1000);});}, reason => {console.error('第二次请求失败', reason);return new Promise(() => {});}).then(value => {console.log('第三次请求成功', value);}, reason => {console.error('第三次请求失败', reason);});</script>
</body>
</html>
Promise 异常穿透
- 当使用 Promise 实例的 then 链式调用时,可以在最后使用 catch 捕获一个事变的回调。当前面任何操作出现了问题,都会传递到最后失败的回调中进行处理。
- 注意:如果不存在 then 的链式调用,则不需要考虑 then 的异常穿透。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>// 异常穿透:当使用 Promise 实例的 then 链式调用时,可以在最后使用 catch 捕获一个事变的回调。当前面任何操作出现了问题,都会传递到最后失败的回调中进行处理。const p = new Promise((resolve, reject) => {setTimeout(() => {resolve('用户数据');}, 1000);});p.then(value => {console.log('第一次请求成功', value);return new Promise((resolve, reject) => {setTimeout(() => {reject('订单数据');}, 1000);});}).then(value => {console.log('第二次请求成功', value);return new Promise((resolve, reject) => {setTimeout(() => {resolve('商品数据');}, 1000);});}).then(value => {console.log('第三次请求成功', value);}).catch(reason => {console.error(reason);});</script>
</body>
</html>
async 和 await
简介
- async 和 await 两种语法的结合可以将异步代码像同步代码一样。
- await 必须包含在 async 修饰的函数中。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>const p = new Promise((resolve, reject) => {// 函数体setTimeout(() => {reject(1);}, 1000);});async function demo() {try {// await 只能返回 Promise 实例对象成功的值const result = await p;console.log(result);} catch (e) {console.log(e);}}demo();</script>
</body>
</html>
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>const p1 = new Promise((resolve, reject) => {// 函数体setTimeout(() => {resolve(1);}, 1000);});const p2 = new Promise((resolve, reject) => {// 函数体setTimeout(() => {resolve(2);}, 2000);});const p3 = new Promise((resolve, reject) => {// 函数体setTimeout(() => {resolve(3);}, 3000);});// 可以使用立即执行函数简化,这种方式和 Promise 实例对象的 then 方法执行的结果是一样的。;(async () => {try {const result1 = await p1;console.log(result1);const result2 = await p2;console.log(result2);const result3 = await p3;console.log(result3);} catch (e) {console.error(e);}})();</script>
</body>
</html>
使用 async 和 await 解决回调地狱问题
- async 和 awit 可以优雅的解决回调地狱问题,比 Promise 的异常穿透更为优雅。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>使用 async 和 await 解决回调地狱</title>
</head>
<body><script>// 用户数据 ==> 订单数据 ==> 商品数据;(async () => {try {let value = await new Promise((resolve, reject) => {setTimeout(() => {resolve('用户数据');}, 1000);});console.log('第一次请求成功', value);value = await new Promise((resolve, reject) => {setTimeout(() => {resolve('订单数据');}, 1000);});console.log('第二次请求成功', value);value = await new Promise((resolve, reject) => {setTimeout(() => {reject('商品数据');}, 1000);});console.log('第三次请求成功', value);} catch (e) {console.error('请求失败', e);}})();</script>
</body>
</html>
async 和 await 的规则
- async 修饰的函数:
- 函数的返回值是 Promise 对象。
- Promise 实例的结果由 async 函数执行的返回值决定。
- await 表达式:
- await 右侧的表达式一般为 Promise 实例对象,但是也可以是其他的值。
- 如果表达式是 Promise 对象,await 的返回值是 Promise 成功的值。
- 如果表达式是其他值,直接将此值作为 await 的返回值。
注意:
- await 必须写在 async 函数中,但是 async 函数中可以没有 await (ES7 规定的)。
- 如果 await 的 Promise 实例失败了,就会抛出异常,需要通过 try…catch 进行捕获。
await 的原理
- 如果我们使用 async 配合 await :表面上不会出现任何回调函数,但是实际上底层是将我们的代码进行的加工,将回调函数还原了回来,最终运行的代码依然有回调,只是程序员看不见而已。
- 换言之,async 配合 await 就是 Promise 中 then 的语法糖。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>await 原理</title>
</head>
<body><script>const p = new Promise((resolve, reject) => {// 函数体setTimeout(() => {resolve(1);}, 2000);});async function demo() {// 相当于 p.then(value=>{ console.log(result) })const result = await p; console.log(result);}demo();</script>
</body>
</html>
宏队列和微队列
- JS 中用来存储待执行回调函数的队列包含 2 个不同特定的列队。
- 宏列队:用来保存待执行的宏任务(回调), 比如: 定时器回调/DOM 事件回调/ajax 回调。
- 微列队:用来保存待执行的微任务(回调), 比如: promise 的回调/MutationObserver 的回调。
- JS 执行时会区别这 2 个队列:
- JS 引擎首先必须先执行所有的初始化同步任务代码。
- 每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行。
示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Title</title>
</head>
<body><script>setTimeout(() => {console.log('timeout');}, 0);Promise.resolve(1).then(value => {console.log('成功1', value);});Promise.resolve(2).then(value => {console.log('成功2', value);});console.log('主线程');</script>
</body>
</html>