Promise本质
Promise所说的异步执行,只是将Promise构造函数中resolve
,reject
方法和注册的callback
转化为eventLoop
的microtask/Promise Job
,并放到EventLoop
队列中等待执行,也就是JavaScript单线程中的“异步执行”
构造函数中的输出执行是同步的,输出1,执行resolve
函数,将Promise
对象状态置为resolved
,输出3。同时注册这个Promise
对象的回调then
函数,将在当前脚本所有同步任务执行完,输出4。整个脚本执行完,stack
清空。
eventloop
检查到stack
为空,再检查microtask队列中是否有任务,发现了Promise
对象的then
回调函数产生的microtask,推入stack
,执行。输出2,event loop
的队列为空,stack
为空,脚本执行完毕。
Promise的异步处理
Promise的两个固有行为:
- 每次对Promise调用
then(..)
,它都会创建并返回一个新的Promise,我们可以将其链接起来; - 不管从
then(..)
调用的完成回调(第一个参数)返回的值是什么,它都会被自动设置为被链接Promise(第一点中的)的完成。
使Promise序列真正能够在每一步有异步能力的关键是:Promise.resolve(..)
会直接返回接收到的真正Promise,或展开接收到的thenable
值,并在持续展开thenable
的同时递归地前进。
尝试去理解一下下面这段代码
Promise的错误处理
一个错误/异常是基于每个Promise的,意味着在链条的任意一点捕获这些错误是可能的,而且这些捕获操作在那一点上将链条“重置”,使它回到正常的操作上来
再看一段代码
第 2 步出错后,第 3 步的拒绝处理函数会捕捉到这个错误。拒绝处理函数的返回值(这段 代码中是 3),如果有的话,会用来完成交给下一个步骤(第 4 步)的 promise
,这样,这 个链现在就回到了完成状态。
注意这句话,解释了为什么最后会出现4,这里要好好理解透彻
总结起来,Promise的步骤
- 调用Promise的
then(..)
会自动创建一个新的Promise从调用返回。 - 在完成或拒绝处理函数内部,如果返回一个值或抛出一个异常,新返回的可链接的Promise就相应地决议。
- 如果完成或拒绝处理函数返回一个Promise,它将会被展开,这样一来,不管它的决议值是什么,都会成为当前
then(..)
返回的链接Promise的决议值。
Promise的穿透
下面这段代码先自己想一下,再去控制台打印
当then()
受非函数的参数时,会解释为then(null)
,这就导致前一个Promise的结果穿透到下面一个Promise。
Promise局限性
- 顺序错误处理
Promise链中的错误很容易被无意中默默忽略掉 - 单一值
Promise只能有一个完成值或一个拒绝理由 - Promise性能
Promise进行的动作要多一些,这自然意味着它也会稍慢一些
更多的工作,更多的保护。这些意味着Promise与不可信任的裸回调相比会更慢一些
Promise使所有一切都成为异步的了,即有一些立即(同步)完成的步骤仍然会延迟到任务的下一步。这意味着一个Promise任务序列可能 比完全通过回调连接的同样的任务序列运行得稍慢一点
编写Promise代码
创建promise
对象
流程如下:
new Promise(fn)
返回一个promise
对象- 在
fn
中指定异步等处理- 处理结果正常的话,调用
resolve
(处理结果值) - 处理结果错误的话,调用
reject
(Error
对象)
- 处理结果正常的话,调用
创建一个用Promise把XHR处理包装起来的名为getURL
的函数
编写promise
对象处理方法
promise
对象添加处理方法主要有以下两种:
promise
对象被resolve
时的处理(onFulfilled
)promise
对象被reject
时的处理(onRejected
)
getURL
只有在通过XHR取得结果状态为200时才会调用resolve
- 也就是只有数据取得成功时,而其他情况(取得失败)时则会调用reject
方法getURL
函数中的resolve(req.responseText);
会将promise
对象变为resolve(Fulfilled)
状态, 同时使用其值调用onFulfilled
函数。
在getURL
的处理中发生任何异常,或者被明确reject
的情况下, 该异常原因(Error
对象)会作为.catch
方法的参数被调用。
Promise API
Promise.resolve
Promise.resolve(promise)
接收到promise
对象参数的时候,返回的还是接收到的promise
对象Promise.resolve(thenable)
接收到thenable
类型的对象的时候,返回一个新的promise
对象,这个对象具有一个then
方法Promise.resolve(object)
接收其他类型的时候(包括JavaScript对或null
等),返回一个将该对象作为值的新promise
对象
Promise.reject
Promise.reject(object)
返回一个使用接收到的值进行了reject
的新的promise
对象。
Promise.then
promise.then(onFulfilled, onRejected)
promise
对象会在变为resolve
或者reject
的时候分别调用相应注册的回调函数。- 当
handler
返回一个正常值的时候,这个值会传递给promise
对象的onFulfilled
方法。 - 定义的
handler
中产生异常的时候,这个值则会传递给promise
对象的onRejected
方法
- 当
Promise.catch
IE8以下Promise#catch标识符冲突问题:
使用Promise#then代替Promise#catch
Promise.all
Promise.all(promiseArray)
参数传递promise
数组中所有的promise
对象都变为resolve
的时候,该方法才会返回, 新创建的promise
则会使用这些promise
的值。
如果参数中的任何一个promise
为reject
的话,则整个Promise.all
调用会立即终止,并返回一个reject
的新的promise
对象。
由于参数数组中的每个元素都是由Promise.resolve
包装(wrap)的,所以Paomise.all
可以处理不同类型的promose
对象。
Promise.race
Promise.race(promiseArray)
参数promise
数组中的任何一个promise
对象如果变为resolve
或者reject
的话, 该函数就会返回,并使用这个promise
对象的值进行resolve
或者reject
。
几个不错的例子
- 理解三种状态
- 链式调用以及返回值
上面说的then()
接收两个函数作为参数,返回值有三种情况,可以返回上面看看
- 异常处理
Promise中的异常由then
参数中第二个回调函数(Promise执行失败的回调)处理,异常信息将作为Promise的值。异常一旦得到处理,then
返回的后续Promise对象将恢复正常,并会被Promise执行成功的回调函数处理。
需要注意p1
、p2
多级then
的回调函数是交替执行的 ,这正是由Promise then
回调的异步性决定的。
resolve
与reject
的区别
这段毫无疑问,resolve
直通车
这段可能会有点疑问,主要在于理解这句代码resolve(Promise.reject('reject'));
再回想一下上面的错误处理以及thenable
对象的展开功能,是不是就好理解一点了,其实可以理解为与运算(&&
),有一个reject
,传下去的也会是reject
但是!!!并不是一直链式的传下去的全都是reject
,只是紧跟着的下一个then
会收到reject
而已,万望好好理解这句话(我这里不展开讲了)
有了第二段的基础,这一段应该就非常好理解了
原文:大专栏 JavaScript知识梳理--Promise