一. 之前异步串联产生的问题
Promise是实现异步编程的一种解决方案。以往的异步操作,只能通过调用回调函数(把一个函数作为参数传到另一个函数中)来进行某些操作,来证明该异步操作已经完成!!!
但是由于串联多个异步操作是一个常见的问题,所以还用回调函数来表示完成状态,就会产生深度嵌套的回调函数(回调地狱)!!
具体问题如下:
<1> 回调嵌套
doA( function(){
doB();
doC( function(){
doD();
} )
doE();
} );
doF();
在项目中,我们往往需要函数嵌套函数执行,可能比上述代码更为难以理解,所以就需要我们花费很多精力去思考它们的执行顺序。what's worse?实际上,我们还会在代码中加入各种各样的逻辑判断,就比如在上面这个例子中,doD() 必须在 doC() 完成后才能完成,万一 doC() 执行失败了呢?我们是要重试 doC() 吗?还是直接转到其他错误处理函数中?当我们将这些判断都加入到这个流程中,很快代码就会变得非常复杂,以至于无法维护和更新。
<2> 回调反转
// 回调函数是否被执行取决于 buy 模块
import {buy} from './buy.js';
buy(itemData, function(res) {
console.log(res)
});
当我们使用回调的时候,这个回调函数是否能接着执行,其实取决于使用回调的那个 API是如何封装的,可能会出现以下情况:
- 回调函数执行多次
- 回调函数没有执行
- 回调函数有时同步执行有时异步执行
而对于这些情况,我们都需要相应的处理措施!!
<3> 回调地域
- 代码难以复用
回调的顺序确定下来之后,想对其中的某些环节进行复用也很困难,牵一发而动全身。
- 堆栈信息被断开
同步执行:如果在A 函数中调用了 B 函数,JavaScript 会先将 A 函数的执行上下文压入栈中,再将 B 函数的执行上下文压入栈中,当 B 函数执行完毕,将 B 函数执行上下文出栈,当 A 函数执行完毕后,将 A 函数执行上下文出栈。所以说,如果出错了,执行上下文栈是还保存的
异步执行:,比如执行 fs.readdir 的时候,其实是将回调函数加入任务队列中,代码继续执行,直至主线程完成后,才会从任务队列中选择已经完成的任务,并将其加入栈中,此时栈中只有这一个执行上下文,如果回调报错,也无法获取调用该异步操作时的栈中的信息,不容易判定哪里出现了错误。
【Promise没有解决该问题】
二. Promise
(1) 创建 Promise 对象
Promise对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个执行器函数作为参数,返回一个Promise实例。
一个 Promise 实例有三种状态,分别是pending、resolved和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由pending 转变resolved或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。
Promise构造函数的参数 ---- 执行器函数 有两个函数参数resolve() 、reject() ,调用两者,可以分别实现不同的状态改变!!!
注意:
Promise构造函数本身是同步的立即执行的函数,所以构造函数内部的代码是立即执行的,但在内部遇到
resolve()、reject()函数时 ,才会进行异步操作,因为执行完resolve()、reject()函数后,就会立马改变Promise的状态,就会调用.then() 方法中注册的处理函数,而这个回调函数属于微任务,会在本轮事件循环的末尾执行。
(2) 期约方法
- Promise.resolve()
实例化一个解决的期约,且该期约的值就是传入的第一个参数
- Promise.reject()
实例化一个拒绝的期约,且期约的拒绝理由就是传入的第一个
- Promise.prototype.then()
该方法接收两个参数 onresolved() 、onRejected() 两个处理程序,如果提供的话,
会在期约进入解决or拒绝状态时执行,因为Promise状态只能改变一次,所以两个处理程序是互斥的!!!
- Promise.prototype.catch()
.catch() 方法用于给期约添加期约拒绝处理程序 onRejected(),相当于一个
语法糖,调用它就相当于调用 Promise.prototype.then(null , onRejected())
- Promise.prototype.finally()
该方法用于给期约添加 onFinally() 处理程序 ,该程序会在期约的状态发生改变后执行(不管是哪种改变都会执行),常常用于添加清理代码!!!
-
Promise.all()
- 接收一个可迭代对象作为参数,可迭代对象中的元素会依次通过 Promise.resolve()转换为期约 - 有待定 ----> 待定 - 全解决 ----> 解决 - 有拒绝 ----> 第一个拒绝 -
Promise.race() - 接收一个可迭代对象作为参数
- 合成为最先解决or拒绝的期约
三. Promise 的局限性
- 错误被吃掉
let promise = new Promise(() => {
throw new Error('error')
});
console.log(2333333);
会正常的打印
233333,说明 Promise 内部的错误不会影响到 Promise 外部的代码,而这种情况我们就通常称为 “吃掉错误”。而正是因为错误被吃掉,Promise 链中的错误很容易被忽略掉,这也是为什么会一般推荐在 Promise 链的最后添加一个 catch 函数,因为对于一个没有错误处理函数的 Promise 链,任何错误都会在链中被传播下去,直到你注册了错误处理函数。
- 无法取消
Promise 一旦新建它就会立即执行,无法中途取消。
- 无法得知 pending 状态
当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。