这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」。
JavaScript_搞清同步/异步,异步问题解决方案?,
今天来看一下Promise是什么?他是怎么解决异步问题。
初识Promise
ECMAScript 6 新增了正式的 Promise(期约)引用类型,支持优雅地定义和组织异步逻辑。
Promise是es6新增的的引用类型,可以使用new操作符实例化,创建一个Promises实例必须要传入一个执行函数作为参数,否则会报错,如下
let p = new Promise()
修改错误,随便写个函数传入就好了
let p = new Promise(() => {})
console.log(Object.prototype.toString.call(p)); //[object Promise]
在上图中输出
p(Promise实例对象),可以发现它有个pending标识,它表示当前这个实例处于待定(pending)状态;
promise 是一个有状态的对象
pending: 待定状态,是promise最初始的状态fulfilled: 兑现状态,代表成功解决rejected: 拒绝状态,代表失败拒绝
Promise 特点
-
状态落定后不可逆
只要从待定转换为兑现或拒绝, Promise的状态就不再改变。
状态并不一定会转换为解决或拒绝,也可能一直处于pending状态中。
可根据不同的状态,控制其行为。
-
Promise的状态是私有的
不能直接通过 JavaScript 检测到,也不能被外部 JavaScript 代码修改;这是为了避免根据读取到的Promise状态,以同步方式处理期约对象
Promise将异步行为封装起来,从而隔离外部的同步代码。
如何控制Promise状态改变
执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。
因为Promise的状态是私有的,所以说状态的改变是在内部操作的。这个时候就要就要来看Promise的执行器函数了,内部操作都在这个函数中完成。
let p1 = new Promise((rsv, rjt) => rsv())
let p2 = new Promise((rsv, rjt) => rjt())
通过上面栗子中,可以发现:
- 这个执行器函数接收两个参数(函数),两个函数参数通常都命名为
resolve()和reject()(上面栗子中故意没有写成通用名🌝)。 resolve(),把状态落定为成功解决。reject(), 把状态落定为失败拒绝,且调用reject()会抛出错误。- 从上面可以看到有Promise 解决值为
undefined, 因为由于上面调用resolve()与reject()时并没有给其内部值或拒绝理由,那么默认就是undefined。否则如下:
let p1 = new Promise((rsv, rjt) => rsv('喜欢'))
let p2 = new Promise((rsv, rjt) => rjt('不喜欢'))
执行器函数作为Promise的初始化程序,它是同步执行的。看接下来的例子
console.log(1)
new Promise(() => console.log('同步'));
new Promise(() => setTimeout(console.log, 0, 'Promise'));
console.log(2)
setTimeout(console.log, 0, 'promise initialized');
console.log(3)
结果如下:
Promise 静态方法
Promise.resolve()
通过调用Promise.resolve() 静态方法可以实例化一个解决的Promise对象。
所以说,Promise并不是一开始就一定处于pending状态。
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
上面的
p1与p2是一样的,并不是一定要通过执行器函数才能改变Promise状态.
resolve静态方法的作用:可以直接实例化一个解决的Promise对象。
resolve静态方法的特点:如果传入的参数本身是一个Promise对象,那它的行为就类似于一个空包装;可以说是一个幂等方法, 如下;
let p1 = new Promise(()=> {});
let p2 = Promise.resolve();
let p3 = Promise.reject()
如上图所示,如果传入resolve的参数是一个Promise对象,那么它会保留这个传入的Promise的状态。
这个静态方法能够包装任何非期约值,包括错误对象,并将其转换为解决的期约。
Promise.reject()
Promise.reject() 这个方法与Promise.resolve方法相似,它会实例化一个拒绝的Promise并抛出一个异步错误 (这个错误不能通过 try/catch 捕获,而只能通过拒绝处理程序捕获)
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
但是,Promise.reject()与Promise.resolve有区别在于,reject没有resolve方法幂等性的特点;他不会保留传入其中的Promise对象的状态,它会以传入的这个Promise作为失败理由返回,如下所示:
let p1 = new Promise(()=> {});
let p2 = Promise.resolve();
let p3 = Promise.reject()
上面提到过,reject抛出的错误不能通过 try/catch 捕获,而只能通过拒绝处理程序捕获,就是因为Promise是异步执行的,异步模式执行只能用异步结构交互。
期约真正的异步特性:它们是同步对象(在同步执行模式中使用),但也是异步执行模式 的媒介。
Promise实例方法
-
then(),为期约实例添加处理程序的主要方法。接收最多两个参数:onResolved 处理程序和 onRejected 处理程序。这两个参数都是可选的,如果提供的话, 则会在期约分别进入“兑现”和“拒绝”状态时执行。
-
catch(),用于给期约添加拒绝处理程序。只接收一个参数:onRejected 处理程序。
-
finally(), 给期约添加onFinally处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。这个方法可以避免 onResolved 和 onRejected 处理程序中出 现冗余代码。但 onFinally 处理程序没有办法知道期约的状态是解决还是拒绝,所以这个方法主要用 于添加清理代码。
Promise 解决回调地狱问题
以前是通过回调解决异步问题的,因为多个异步执行函数依赖执行结果,造成深入嵌套,难以维护,俗称“回调地狱”
那么如何使用Promise 解决回调地狱问题?
简单来说,就是通过promise的链式调用解决回调函数的嵌套问题。
promise 通过 then 的方式收集了回调,使用时把 resolve 传入的值作为参数传递进回调。回调会在 resolve() 之后触发。于是就把下次回调的使用权交给了这次的 resolve,所以可以使用链式调用的方式编写本应该嵌套的回调。
// 伪代码,深度嵌套
ajaxA({
success(){
ajaxB({
success(){
ajaxB({
success(){
}
})
}
})
}
})
// 使用promise
ajaxA().then().then().then()