2.4 使用 2:使用 promise 封装基于定时器的异步
2.5 使用 3:使用 promise 封装 ajax 异步请求
3.1 Promise 构造函数:new Promise (executor) { }
3.2 Promise.prototype.then 方法:Promise 实例. then(onFulfilled, onRejected)
3.3 Promise.prototype.catch 方法:Promise 实例. catch (onRejected)
3.4 Promise.resolve(value) / Promise.reject( reason )
4.3 then 的链式调用解决回调地狱(不是最优秀的方法)
4.6 async - await 解决回调地狱(终极解决方案
函数对象:函数作为对象使用时,简称为函数对象。
function Person(name, age) { this.name = name; this.age = age; } Person.sex = 'boy'; console.log(Person.sex); console.log(Person.name); // Person.name 默认是构造函数名,无法自己定义
实例对象:new 构造函数或类产生的对象,我们称之为实例对象。
const p = new Person('zs', 20); console.log(p); // Person {name: "zs", age: 20}
我们定义的,我们没有调用,最终执行了。
(1)同步的回调函数:
理解:立即在主线程上执行,不会放入回调队列中。
举例:数组遍历相关的回调函数 / Promise 的 executor 函数。
(2)异步的回调函数:
理解:不会立即执行,会放入回调队列以后执行。
举例:定时器回调 / ajax 回调 / Promise 的成功、失败的回调。
JS 执行顺序:
事件循环:
| Error | 所有错误的父类型 |
| ReferenceError | 引用的变量不存在 |
| TypeError | 数据类型不正确 |
| SyntaxError | 语法错误 |
捕获错误:try{ } catch(){ }
MDN 文档:try...catch - JavaScript | MDN
- try {
- nonExistentFunction();
- } catch (error) {
- console.error(error);
- // expected output: ReferenceError: nonExistentFunction is not defined
- // Note - error messages will vary depending on browser
- }
抛出错误:throw error
message 属性:错误相关信息。
stack 属性:记录信息。
抽象表达:
(1)Promise 是一门新的技术(ES6 规范)。
(2)Promise 是 JS 中进行异步编程的新解决方案(旧方案是:单纯使用回调函数)。
具体表达:
(1)从语法上来说:Promise 是一个构造函数。
(2)从功能上来说:promise 对象用来封装一个异步操作并可以获取其成功 / 失败的结果值。
理解:
(1)Promise 不是回调,是一个内置的构造函数,是程序员自己 new 调用的。
(2)new Promise 的时候,要传入一个回调函数(executor 函数),它是同步的回调,会立即在主线程上执行。
(3)executor 函数会接收到 2 个参数,它们都是函数,分别用形参:resolve、reject 接收。
① 调用 resolve,会让 Promise 实例状态变为:成功 (fulfilled),同时可以指定成功的 value。
② 调用 reject,会让 Promise 实例状态变为:失败 (rejected),同时可以指定失败的 reason。
注:回调函数executor本身 是同步的,executor里面的函数是异步的。
每一个 Promise 实例都有 3 种状态,分别是:初始化(pending)、成功(fulfilled)、失败(rejected)。
每一个 Promise 实例被 new 出来的那一刻,状态都是初始化(pending)。
Promise 的状态只能改变一次,并且改变只有两种:pending => fulfilled;pending => rejected。

new Promise(executor) 构造函数
Promise.prototype.then() 方法
1、创建Promise的实例对象(pending状态),传入executor函数
2、在executor中启动异步任务(定时器、ajax请求)
3、根据异步任务的结果,做不同处理:
3.1 如果异步任务成功了:
我们调用resolve(value),让Promise实例对象状态变为成功(fulfilled) ,同时指定成功的value
3.2.如果异步任务失败了:
我们调用reject(reason),让Promise实例对象状态变为失败(rejected),同时指定失败的reason
4、通过then方法为Promise的实例指定成功、失败的回调函数,来获取成功的value、失败的reason
注意: then方法所指定的:成功的回调、失败的回调,都是异步的回调。
5、关于状态的注意点:
1、三个状态:
pending:未确定的 ----- 初始状态
fulfilled:成功的 ------ 调用resolve( ) 后的状态
rejected:失败的 ------ 调用reject( ) 后的状态
2、两种状态改变
pending => fulfilled
pending => rejected
3、状态只能改变一次! !
4、一个promise指定多个成功/失败回调函数,都会调用吗? 会!
- // 1) 创建 promise 对象(pending 状态), 指定执行器函数
- const p = new Promise((resolve, reject) => {
- // 2) 在执行器函数中启动异步任务
- setTimeout(() => {
- const time = Date.now()
- // 3) 根据结果做不同处理
- if (time % 2 === 1) {
- // 3.1) 如果成功了, 调用 resolve(), 指定成功的 value, 变为 resolved 状态
- resolve('成功的值 '+ time) } else {
- // 3.2) 如果失败了, 调用 reject(), 指定失败的 reason, 变为rejected 状态
- reject('失败的值' + time) }
- }, 2000)
- })
- // 4) 能 promise 指定成功或失败的回调函数来获取成功的 vlaue 或失败的 reason
- p.then(
- value => { // 成功的回调函数 onResolved, 得到成功的 vlaue
- console.log('成功的 value: ', value)
- },
- reason => { // 失败的回调函数 onRejected, 得到失败的 reason
- console.log('失败的 reason: ', reason)
- }
- )
- function doDelay(time) {
- // 1. 创建 promise 对象
- return new Promise((resolve, reject) => {
- // 2. 启动异步任务
- console.log('启动异步任务')
- setTimeout(() => {
- console.log('延迟任务开始执行...')
- const time = Date.now() // 假设: 时间为奇数代表成功, 为偶数代表失败
- if (time % 2 === 1) { // 成功了
- // 3. 1. 如果成功了, 调用 resolve()并传入成功的 value
- resolve('成功的数据 ' + time)
- } else { // 失败了
- // 3.2. 如果失败了, 调用 reject()并传入失败的 reason
- reject('失败的数据 ' + time)
- }
- }, time)
- })
- }
- const promise = doDelay(2000)
- promise.then(
- value => {
- console.log('成功的 value: ', value)
- },
- reason => {
- console.log('失败的 reason: ', reason)
- },
- )
-
-
- /*
- 可复用的发 ajax 请求的函数: xhr + promise
- */
- function promiseAjax(url) {
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest()
- xhr.onreadystatechange = () => {
- if (xhr.readyState !== 4) return
- const { status, response } = xhr
- // 请求成功, 调用 resolve(value)
- if (status >= 200 && status < 300) {
- resolve(JSON.parse(response))
- } else { // 请求失败, 调用 reject(reason)
- reject(new Error('请求失败: status: ' + status))
- }
- }
- xhr.open("GET", url)
- xhr.send()
- })
- }
- promiseAjax('https://api.apiopen.top2/getJoke?page=1&count=2&type=video').then(
- data => {
- console.log('显示成功数据', data)
- },
- error => {
- alert(error.message)
- })
-
executor 函数:是同步执行的,(resolve, reject) => { }
resolve 函数:调用 resolve 将 Promise 实例内部状态改为成功 (fulfiled)。
reject 函数:调用 reject 将 Promise 实例内部状态改为失败 (rejected)。
说明:executor 函数会在 Promise 内部立即同步调用,异步代码放在 executor 函数中。
onFulfilled:成功的回调函数 —— (value) => { }
onRejected:失败的回调函数 —— (reason) => { }
注意:then 方法会返回一个新的 Promise 实例对象。
onRejected:失败的回调函数 —— (reason) => { }
说明:catch 方法是 then 方法的语法糖,相当于:then(undefined, onRejected)。
executor里执行的是reject函数:
第一个p.then方法里的回调函数只指定了成功的回调,而没有指定失败的回调,会产生报错,未捕获异常(Uncaught ...)
第二个p.catch方法实际上是p.then的语法糖,它默认不指定成功的回调,只指定失败的回调,然后输出reject函数的信息。
另外:如果executor里执行的是resolve函数,而下面没有指定成功的回调,不会报错。
Promise.resolve(value):
说明:用于快速返回一个状态为 fulfilled 或rejected的 Promise 实例对象。
备注:value 的值可能是:(1)非 Promise 值,例如数组,字符串等等。(2)Promise 值。
① 如果先调用了reject,返回失败的Promise值,再调用resolve,最后仍然返回失败的Promise值。
②如果先调用了resolve,返回成功的Promise值,再调用reject,最后执行失败的Promise回调函数,但reason是之前的成功的Promise值
Promise.reject(reason):用于快速返回一个状态必为 rejected 的 Promise 实例对象。

![]()
- // 测试:如果方法给的是 p0 参数,打印出各种可能的结果
- // 为了方便看,不进行代码的注释。
-
- // resolve - resolve
- const p0 = Promise.resolve('ok');
- const p = Promise.resolve(p0); // succ: ok
-
- // reject - reject
- const p0 = Promise.reject('no!');
- const p = Promise.reject(p0); //fail: Promise {
: "no!"} + 报错:Uncaught (in promise) no! -
- // resolve - reject
- const p0 = Promise.resolve('ok');
- const p = Promise.reject(p0); // fail: Promise {
: "ok"} -
- // reject - resolve
- const p0 = Promise.reject('no!');
- const p = Promise.resolve(p0); // fail: no!
-
- p.then(
- (value) => { console.log('succ: ', value); },
- (reason) => { console.log('fail: ', reason); }
- )
-
promiseArr:包含 n 个 Promise 实例的数组。
说明:返回一个新的 Promise 实例,只有所有的 promise 都成功才成功,成功的值是所有Promise成功的值的集合;只要有一个失败了就直接失败,一遇到失败的Promise就直接返回失败的值,不关心后面的promise值。
-
- const p1 = Promise.resolve('0');
- const p2 = new Promise((resolve, reject) => {
- setTimeout(() => {
- // resolve('500');
- reject('500');
- }, 500)
- });
- const p3 = new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve('2000');
- }, 2000)
- })
- const x = Promise.all([p1, p2, p3]);
- const then = x.then(
- (value) => { console.log('success', value); },
- (reason) => { console.log('fail', reason); } // 'fail', '2000'
- )
-
promiseArr:包含 n 个 Promise 实例的数组。
说明:返回一个新的 Promise 实例,返回第一个promise实例的值。
1、如何改变一个Promise实例的状态?

Promise状态只能改变一次,上面会返回成功,后面的未定义异常不会引起Promise状态改变。
2、改变Promise实例的状态和指定回调函数谁先谁后?
①都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调

②如何先改状态再指定回调?
延迟一会 再调用 then( )

③Promise实例什么时候才能得到数据?
如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据。
如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据。
3、Promise实例.then()返回的是一个【新的Promise实例】,它的值和状态由什么决定?
1、简单表达:then( ) 所指定的回调函数执行的结果决定 。
2、详细表达:
(1) 如果then所指定的回调返回的是 非Promise值 a:
那么【新Promise实例】状态为:成功(fulfilled),成功的value为a 。
(2) 如果then所指定的回调返回的是一个Promise实例 p:
那么【新Promise实例】的状态、值,都与p一致 。
(3) 如果then所指定的回调抛出异常:
那么【新Promise实例】状态为rejected,reason为抛出的那个异常。

p.then输出成功了1,a,返回值为900,是非Promise值,
然后调用x.then,返回成功了2,900。

p.then输出失败了1,a,返回值为undefined,是非Promise值,
然后调用x.then,返回成功了2,undefined。


什么是回调地狱?
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件。
例如:要求在第一次请求成功后开启第二次请求,第二次请求后开启第三次请求,导致层层嵌套。
回调地狱的缺点:不便于程序员阅读,不便于异常处理,不便于后期维护。
Promise 实例. then( ) 返回的是一个新的 Promise 实例,它的值和状态由 then() 所指定的回调函数执行的结果决定。
(1)如果 then 所指定的回调返回的是非 Promise 值 a:那新的 Promise 实例状态为成功 (fulfillled),成功的 value 为 a。
-
- const p = new Promise((resolve, reject) => {
- resolve('a');
- })
-
- const x = p.then(
- (value) => { console.log('succ_then1:', value); }, // succ_then1: a
- (reason) => { console.log('fail_then1:', reason); }
- )
-
- x.then(
- (value) => { console.log('succ_then2:', value); }, // succ_then2: undefined
- (reason) => { console.log('fail_then2:', reason); }
- )
-
-
- const p = new Promise((resolve, reject) => {
- resolve('a');
- })
-
- const x = p.then(
- (value) => { console.log('succ_then1:', value); return false }, // succ_then1: a
- (reason) => { console.log('fail_then1:', reason); }
- )
-
- x.then(
- (value) => { console.log('succ_then2:', value); }, // succ_then2: false
- (reason) => { console.log('fail_then2:', reason); }
- )
-
(2)如果 then 所指定的回调返回的是 Promise 实例 p:那新的 Promise 实例的状态、值都与 p 一致。
-
- const p = new Promise((resolve, reject) => {
- resolve('a');
- })
-
- const x = p.then(
- (value) => { console.log('succ_then1:', value); return Promise.resolve('a') }, // succ_then1: a
- (reason) => { console.log('fail_then1:', reason); }
- )
-
- x.then(
- (value) => { console.log('succ_then2:', value); }, // succ_then2: a
- (reason) => { console.log('fail_then2:', reason); }
- )
-
-
- const p = new Promise((resolve, reject) => {
- resolve('a');
- })
-
- const x = p.then(
- (value) => { console.log('succ_then1:', value); return Promise.reject('a') }, // succ_then1: a
- (reason) => { console.log('fail_then1:', reason); }
- )
-
- x.then(
- (value) => { console.log('succ_then2:', value); },
- (reason) => { console.log('fail_then2:', reason); } // fail_then2: a
- )
-
(3)如果 then 所指定的回调抛出异常:那新的 Promise 实例状态为 rejected,reason 为抛出的异常。
-
- const p = new Promise((resolve, reject) => {
- resolve('a');
- })
-
- const x = p.then(
- (value) => { console.log('succ_then1:', value); throw 404 }, // succ_then1: a
- (reason) => { console.log('fail_then1:', reason); }
- )
-
- x.then(
- (value) => { console.log('succ_then2:', value); },
- (reason) => { console.log('fail_then2:', reason); } // fail_then2: 404
- )
-
综合案例:
-
- const p = new Promise((resolve, reject) => {
- resolve('a');
- })
-
- p.then(
- (value) => { console.log('succ_then1:', value); throw 404 }, // succ_then1: a
- (reason) => { console.log('fail_then1:', reason); return 10 }
- ).then(
- (value) => { console.log('succ_then2:', value); return 100 },
- (reason) => { console.log('fail_then2:', reason); return Promise.reject('20') } // fail_then2: 404
- ).then(
- (value) => { console.log('succ_then3:', value); return true },
- (reason) => { console.log('fail_then3:', reason); return false } // fail_then3: 20
- ).then(
- (value) => { console.log('succ_then4:', value); },
- (reason) => { console.log('fail_then4:', reason); } // succ_then4: false
- )
-
解释:第一次请求成功的时候,调用 value => { } 这个成功的回调,返回的是一个 Promise 实例 (第二次请求的实例)。由上一点 then() 方法的相关说明可知,如果 then 所指定的回调返回的是 Promise 实例 p,那新的 Promise 实例的状态、值都与 p 一致。所以实际上我们是将第二次请求的 Promise 实例作为第一个 then 的返回值传递下去。
- promiseAjax(url)
- .then(
- value => {
- console.log('显示第1次成功的数据', value);
- // 返回第二次请求的实例
- return promiseAjax(url)
- },
- reason => { alert(reason.message); }
- )
完整 code:
-
- // 定义一个发送请求的函数,返回一个新的 Promise 实例(封装了 ajsx 异步任务)
- function promiseAjax(url) {
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhr.onreadystatechange = () => {
- if (xhr.readyState !== 4) return;
- const { status, response } = xhr;
- if (status >= 200 && status < 300) {
- // 请求成功, 调用 resolve(value)
- resolve(JSON.parse(response));
- } else {
- // 请求失败, 调用 reject(reason)
- reject(new Error('请求失败: status: ' + status));
- }
- }
- xhr.open("GET", url);
- xhr.send();
- })
- }
-
- // 定义正确和错误的 url 地址便于之后的测试
- const url = 'https://api.apiopen.top/api/getHaoKanVideo?page=0&size=2';
- const url_error = 'https://api.apiopen.top/api22/getHaoKanVideo?page=0&size=2';
-
- // then 的链式调用解决回调地狱问题
- promiseAjax(url)
- .then(
- value => {
- console.log('显示第1次成功的数据', value);
- return promiseAjax(url)
- },
- reason => { alert(reason.message); }
- )
- .then(
- value => {
- console.log('显示第2次成功的数据', value);
- return promiseAjax(url)
- },
- reason => { alert(reason.message); }
- )
- .then(
- value => {
- console.log('显示第3次成功的数据', value);
- return promiseAjax(url)
- },
- reason => { alert(reason.message); }
- )
- .then(
- value => {
- console.log('显示第4次成功的数据', value);
- return promiseAjax(url)
- },
- reason => { alert(reason.message); }
- )
-
-
问题产生:如果我们按照 4.3 的代码运行,假设第 2 次请求发送失败,那么会抛出一个 ERROR 错误,调用 reason => {} 失败的回调,但可惜的是我们并没有给失败的回调返回值,所以返回 undefined。而 undefined 恰好是非 Promise 值,这导致在之后的请求中变为成功!这显然是不对的。
解决的问题:请求失败后就不再继续请求。
解决方法:为每一个失败的回调返回一个状态为 pending 的 Promise 实例。
- promiseAjax(url)
- .then(
- value => {
- console.log('显示第1次成功的数据', value);
- return promiseAjax(url_error)
- },
- reason => { alert(reason.message); return new Promise(() => { }) }
- )
- .then(
- value => {
- console.log('显示第2次成功的数据', value);
- return promiseAjax(url)
- },
- reason => { alert(reason.message); return new Promise(() => { }) }
- )
问题的产生:我们在每一个 then() 方法中都要指定失败的回调,并且需要返回值以中断 Promise 链,这些失败的回调代码极其相似,显得繁琐。
解决方案:使用 .catch() 方法兜底。
基本思想:我们使用 .catch() 方法为所有错误指定一个失败的回调,实际的思想是,虽然我们没有在 then() 方法中指定失败的回调,但是底层为我们补了 reason => {throw reason},如果 then 所指定的回调抛出异常,那新的 Promise 实例状态为 rejected,reason 为抛出的异常。所以实际上这个 reason 不断通过失败的回调传递给 .catch() 方法,最终抛出异常。
- promiseAjax(url)
- .then(
- value => {
- console.log('显示第1次成功的数据', value);
- return promiseAjax(url_error)
- },
- // 我们虽然没有写失败的回调,但实际上底层为我们补了以下代码:
- // reason => {throw reason}
- )
- .then(
- value => {
- console.log('显示第2次成功的数据', value);
- return promiseAjax(url)
- },
- // 我们虽然没有写失败的回调,但实际上底层为我们补了以下代码:
- reason => {throw reason}
- )
- .catch(
- reason => { alert(reason.message); }
- )
语法说明:
(1)async 修饰的函数:
① 函数的返回值为 promise 对象。
② promise 实例的结果由 async 函数执行的返回值决定。
(2)await 表达式:
① 如果表达式是 promise 实例对象,await 后的返回值是 promise 成功的值。
② 如果表达式是其他值,直接将此值作为 await 的返回值(相当于赋值操作,好像没啥用的样子...)
(3)注意:
① await 必须写在 async 函数中,但 async 函数中可以没有 await(单纯函数前面写 async 好像也没什么用的样子...)。
② 如果 await 的 promise 实例对象失败了,就会抛出异常,需要通过 try...catch 来捕获处理。
async - await 语法:
// 函数形式写法 async function demo() { try { const result = await promiseAjax(url); console.log(result); console.log(100); } catch (error) { console.log(error); } } demo(); // 箭头函数形式写法 // 注意:如果前面没分号,需要在函数前面加分号或者感叹号,否则会报错。 (async () => { try { const result = await promiseAjax(url); console.log(result); console.log(100); } catch (error) { console.log(error); } })()
await 的原理:
若我们使用 async 配合 await 这种写法:
1、表面上不出现任何的回调函数。
2、但实际上底层把我们写的代码进行了加工,把回调函数 “还原” 回来了。
3、最终运行的代码是依然有回调的,只是程序员没有看见。
(async () => { try { // 程序员“实际上”的写法 const result = await promiseAjax(url); console.log(result); console.log(100); // 浏览器翻译后的代码(表面上没有调then,实际上调了then) promiseAjax(url).then( (value) => { console.log(value); console.log(100); } ) } catch (error) { console.log(error); } })() console.log('主线程'); // 输出:先执行主线程,函数瞬间调用完,将异步函数推进队列,等待调用执行。 // 主线程 // {code: 200, message: "成功!", result: {…}} // 100
async / await 请求成功


as失败


优势:
1、指定回调函数的方式更加灵活:
旧的:必须在启动异步任务前指定
promise:启动异步任务=〉返回promie对象=〉给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
2、支持链式调用,可以解决回调地狱问题
(1)什么是回调地狱:
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。
(2)回调地狱的弊病:
代码不便于阅读、不便于异常的处理。
(3)一个不是很优秀的解决方案:
then的链式调用
(4)终极解决方案:
async/ await (底层实际上依然使用then的链式调用)
JS 中用来存储待执行回调函数的队列包含 2 个不同特定的列队:
宏队列:用来保存待执行的宏任务(回调),比如:定时器回调、DOM 事件回调、ajax 回调。微队列:用来保存待执行的微任务(回调),比如:promise 的回调、MutationObserver 的回调。目前接触到的微任务就只有Promise的回调。
执行:JS 引擎首先必须先执行所有的初始化同步任务代码,每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行,也就是微任务优先级比宏任务高,微任务执行完之后再执行宏任务,且与微任务所处的代码位置无关。
宏队列:[宏任务1,宏任务2.....]
微队列:[微任务1.微任务.....]
规则:每次要执行宏队列里的一个任务之前,先看微队列里是否有待执行的微任务。
1、如果有,先执行微任务。
2、如果没有,按照宏队列里任务的顺序,依次执行。
微任务先于宏任务执行。
- setTimeout(() => {
- console.log('timeout1');
- })
- setTimeout(() => {
- console.log('timeout2');
- });
- Promise.resolve(3).then(
- value => { console.log('成功了1'); }
- )
- Promise.resolve(4).then(
- value => { console.log('成功了2'); }
- )
宏任务里面调用了微任务:
宏任务队列:3s后 timeout2; 5s 后 timeout1, 然后将 timeout3推入宏任务,再将成功了5 推入微任务。
微任务队列:成功了3;成功了4;成功了5
执行结果:微任务先执行:成功了3;成功了4;宏任务执行:间隔3秒的 timeout2 先执行,间隔5s的 timeout1 再执行,然后将 timeout3推入宏任务,再将成功了5 推入微任务。微任务先执行成功了5,然后执行宏任务timeout3。
注:宏任务里面可能存在将微任务放入微队列,这时候微队列先执行。
- setTimeout(() => {
- console.log('timeout1');
- setTimeout(() => {
- console.log('timeout3');
- })
- Promise.resolve(5).then(
- value => { console.log('成功了5'); }
- )
- }, 5000)
- setTimeout(() => {
- console.log('timeout2');
- }, 3000)
- Promise.resolve(3).then(
- value => { console.log('成功了3'); }
- )
- Promise.resolve(4).then(
- value => { console.log('成功了4'); }
- )
-
- setTimeout(() => {
- console.log('0');
- });
- new Promise((resolve, reject) => {
- console.log('1');
- resolve();
- }).then(() => {
- console.log('2');
- new Promise((resolve, reject) => {
- console.log('3');
- resolve();
- }).then(() => {
- console.log('4');
- }).then(() => {
- console.log('5');
- })
- }).then(() => {
- console.log('6');
- })
- new Promise((resolve, reject) => {
- console.log('7');
- resolve();
- }).then(() => {
- console.log('8');
- })
-



输出:1 7 2 3 8 4 6 5 0