前人栽树
许大仙 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); // false
console.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 => {
// 100
console.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 => {
// 100
console.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 -2
console.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 333
console.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>