
Promise
Table of Contents
Promise
是什么?
promise是ES6引入的异步编程新的解决方案, 从语法上来说它是一个构造函数,可以实例化对象,封装异步操作,获取成功或失败的结果
了解js异步执行, JS是单线程的, 浏览器不是单线程的, js调用的某些api也不是单线程的
所以只是
执行代码的线程是单线程
event loop 是用于检测 call stack 和 queue, 一旦call stack 所有任务都结束了(或者说 只要queue发生变化), event loop 就会从queue中取出第一个回调开始执行
注: 倒计时器只要开始之后就会自动开始倒计时, 不管call stack 和 queue中是什么情况
其优点是:支持链式调用,可以解决回调地狱问题、指定回调的方式更为灵活
-
抽象表达:
Promise 是一门新的技术(ES6 规范)
Promise 是 JS 中进行异步编程的新解决方案
备注:旧方案是单纯使用回调函数
-
具体表达:
从语法上来说: Promise 是一个构造函数
从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值
异步编程
-
fs 文件操作
require('fs').readFile('./index.html', (err,data)=>{})
-
数据库操作
-
AJAX
$.get('/server', (data)=>{})
-
定时器
setTimeout(()=>{}, 2000);
为什么
1.指定回调函数的方式更加灵活
-
旧的: 必须在启动异步任务前指定
-
promise: 启动异步任务=> 返回promie对象=> 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
-
支持链式调用,可以解决回调地狱问题
什么是回调地狱?
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件
回调地狱的缺点? 不便于阅读, 不便于异常处理
基本使用
需求
点击按钮, 1s 后显示是否中奖(30%概率中奖)
若中奖弹出 恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券
若未中奖弹出 再接再厉
实现:
-
html
<div class="container"> <h2 class="page-header">Promise 初体验</h2> <button class="btn btn-primary" id="btn">点击抽奖</button> </div>
-
script
-
实践练习 fs
-
实践练习2 AJAX
-
封装一个函数
promisify
Promise封装AJAX请求
Promise 状态的改变
实际上就是promise实例对象中的一个属性, 叫做 PromiseState
-
pending 变为 resolved / fulfilled
-
pending 变为 rejected
说明: 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据
成功的结果数据一般称为 value, 失败的结果数据一般称为 reason
Promise 对象的值
实例对象中的另一个属性 『PromiseResult』
保存着异步任务『成功/失败』的结果
- resolve
- reject
Promise 的基本流程
Promise API
有三个, 分别是
-
new Promise(executor)
-
then方法
-
catch
一般来说回调函数是执行完所有代码后执行的(异步),这里111在222前面输出证明是同步
promise内部的操作是同步代码,then方法是异步代码
catch等于是把then的后一个函数拆出来,单独做成语法糖
Promise 的方法
- Promise.resolve(); 属于Promise函数对象, 并不属于实例对象
-
promise.reject();
跟上面一样, 只不过都是拒绝
-
promise.all();
-
promise.race();
类似赛跑, 谁先改变状态, 谁就决定promise 的结果
promise 的几个关键问题
-
如何修改promise的状态
throw 'error!';
-
如果使用then方法为一个promise对象指定多个回调, 那么这些回调会不会都执行?
会, 当 promise 改变为对应状态时都会调用
回调1 和 2 都会执行, 但如果我上面没有调用resolve, 那下面的回调就不会执行
-
改变 promise 状态 和 指定回调函数(then或者catch) 谁先谁后?
看同步还是异步, 改变状态里面是一个异步任务的时候, 那就是then先被指定, 后改变状态, 然后再执行回调
拿到数据指的是: 指定的回调函数什么时候执行
要注意: 这里指的是then回调函数的执行, 不是里面的成功和失败的回调结果
所以:
如果我先改变了状态(直接调用resolve), then方法在调用的时候就会执行里面的回调去处理成功或失败的结果
如果我先执行回调(then), 后续改变状态, 那么改变状态以后再去调用成功或失败的结果
指定回调---改变状态---执行回调
是先指定回调,不是执行回调 !!!
-
promise.then() 返回的新 promise 的结果状态由什么决定
由 then()指定的回调函数执行的结果决定
-
promise 如何串连多个操作任务?
为什么输出undefined?
因为此时已经不是promise调用then方法了,success是一个字符串对象,不是promise对象
这里要注意, 如果这个指定的回调函数(then里面的回调)执行后没有返回值, 返回的结果就是undifined(可能是一个成功的promise, 可能是一个失败的promise, 只不过成功或失败的值为undifined)
-
异常穿透
-
如何中断Promise链
手写Promise
注: 我这里左边写错了,应该是
let p = new Promise((resolve,reject)=>{...})
声明resolve, reject , 可以接收参数的
resolve可以做两件事
1.改变对象状态(就是实例对象上的一个属性 promiseState)
2.设置结果值 promiseResult
所以我们要给实例对象添加这两个属性
接下来在resolve里面分别设置
结果发现输出为:
为什么数据和状态都没发生改变呢?
因为resolve里面的this指向为window, 因此我们要调整this指向
现在
接下来, 我们要实现
throw抛出异常来改变状态
(只要抛出异常状态就为失败了)
要通过try catch, 那么 try catch 写在哪里呢?
接下来, 需求是
promise状态只能修改一次
以上代码, 正常的执行结果应该是resolve, 成功
但我们自己写的代码最后输出结果是reject, 因此我们要解决这个问题
怎么解决?
在走代码之前加一个判断即可
then方法当中执行回调
在then方法中补全逻辑
异步任务then的实现
首先要明白改变 promise 状态 和 指定回调函数(then或者catch) 谁先谁后
, 我们知道, 是 指定回调---改变状态---执行回调
这时, 是异步的, 接下来, then方法已经被指定了, 但状态还没改变
callback定义
接下来就是执行回调
, 那就要在各自的回调函数里面执行
指定多个回调的实现
现在页面只会输出我画线的这句话
正常情况控制台和alert都会出现OK
为什么我们的只出现了alert呢?
因为
所以要把callback变成一个数组, 调用方法push 保存回调函数
//声明一个属性, 用于保存回调函数
this.callbacks = [];
所以接下来, 每一个回调都要执行, 就要遍历数组, 执行里面的每一个函数
同步修改状态then方法返回的结果
我们知道then方法应该返回一个 promise对象, 返回的结果是由then方法指定的回调函数的执行结果决定的
所以我们要考虑这几种情况, 先去then方法里修改, 让其return 一个 Promise
异步修改状态then方法返回的结果
然后因为要抛出错误, 所以try catch
完善then方法
then方法当中reject 我们没写全
看到上面的图, 我们发现这些函数都有try catch, 而且里面的内容很像, 所以我们要进行函数封装
Async 和 await
async
-
函数的返回值为 promise 对象
-
promise 对象的结果由 async 函数执行的返回值决定
await
The await
operator is used to wait for a Promise
and get its fulfillment value. It can only be used inside an async function or at the top level of a module.
-
await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
-
如果表达式是 promise 对象, await 返回的是 promise 成功的值
-
如果表达式是其它值, 直接将此值作为 await 的返回值
注意
- await 必须写在 async 函数中, 但 async 函数中可以没有 await
- 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理
在异步操作中,通常使用
async
和await
来确保在异步操作完成之前不会继续执行后续代码。这样可以更好地处理异步操作的结果,例如处理登录成功或失败的情况,或者在获取数据后更新用户界面。使用
async
和await
确实会让异步操作看起来像同步操作,因为它们会暂停函数的执行,等待异步操作完成。这是一种改进异步代码可读性的方式,因为它避免了回调嵌套和提供了更直观的控制流。但关键是它并没有真正将异步操作变成同步操作,而是使代码更易于理解和维护。在异步函数内部使用
await
会等待异步操作完成,但这不会阻塞整个应用程序的执行。其他未被await
影响的代码仍然可以继续执行。这意味着你可以在等待异步操作的同时,执行其他非依赖于该异步操作结果的任务。这有助于应用程序保持响应性。
async + await
案例一:
案例二: