想用Promise異步實現一個遞歸調用的接口,用來做簡單AI的動作序列。發現一開始接觸這個then的時候,不是很清楚,參考了網上的一些寫法,改成自己的有問題,所以先靜下心來研究一下這個調用的順序問題
例子
先看個例子,參考[1]
new Promise((resolve, reject) => { console.log("promise") resolve() }) .then(() => { // 執行.then的時候生成一個promise是給最后一個.then的 console.log("then1") new Promise((resolve, reject) => { console.log("then1promise") resolve() }) .then(() => {// 執行這個.then的時候,生成的promise是下面一個then的 console.log("then1then1") }) .then(() => { console.log("then1then2") }) }) .then(() => { // 這個 console.log("then2") })
結果:
promise then1 then1promise then1then1 then2 then1then2
問題:主要是疑惑then2在then1then1和then1then2之間
理論
為了方便分析,列幾個自己的理解,后面解釋問題的時候方便。尤其是理論4和理論5
理論1:Promise是一個對象
Promise是一個對象,他包含了
- 自己的函數體:new時候傳進去的一個參數,是一個函數
- 狀態:fulfilled, pending, rejected
- 異步函數隊列:pending狀態下放在這里的then回調函數體
理論2:resolve/reject是用來改變Promise對象的
- resolve:pending改成fulfilled
- reject:pending改成rejected
另外, resolve執行的時候會去檢查Promise自己的隊列,如果不是空的,會把回調函數體塞到nextTick隊列中。 代碼參考[4]
handlers.resolve = function (self, value) { var result = tryCatch(getThen, value); if (result.status === 'error') { return handlers.reject(self, result.value); } var thenable = result.value; if (thenable) { safelyResolveThenable(self, thenable); } else { self.state = FULFILLED; self.outcome = value; var i = -1; var len = self.queue.length; while (++i < len) { self.queue[i].callFulfilled(value); } } return self; };
理論3:Promise的t’hen/catch方法執行后返回的也是一個Promise對象
參考[2],返回的Promise對象支持了鏈式調用
理論4:then函數返回的Promise對象什么時候resolve看返回值
參考[2]返回值一節
(1)返回了一個值,那么 then 返回的 Promise 將會成為接受狀態,並且將返回的值作為接受狀態的回調函數的參數值。 (2)沒有返回任何值,那么 then 返回的 Promise 將會成為接受狀態,並且該接受狀態的回調函數的參數值為 undefined。 (3)拋出一個錯誤,那么 then 返回的 Promise 將會成為拒絕狀態,並且將拋出的錯誤作為拒絕狀態的回調函數的參數值。 (4)返回一個已經是接受狀態的 Promise,那么 then 返回的 Promise 也會成為接受狀態,並且將那個 Promise 的接受狀態的回調函數的參數值作為該被返回的Promise的接受狀態回調函數的參數值。 (5)返回一個已經是拒絕狀態的 Promise,那么 then 返回的 Promise 也會成為拒絕狀態,並且將那個 Promise 的拒絕狀態的回調函數的參數值作為該被返回的Promise的拒絕狀態回調函數的參數值。 (6)返回一個未定狀態(pending)的 Promise,那么 then 返回 Promise 的狀態也是未定的,並且它的終態與那個 Promise 的終態相同;同時,它變為終態時調用的回調函數參數與那個 Promise 變為終態時的回調函數的參數是相同的。
理論5:執行順序為:同步執行 > nextTick隊列 > setTimeout隊列 > Promise對象私有隊列
參考[3],文章寫的不錯。大體講明白了Promise和then的原理了。因為文章里面的截圖不是很清楚,我還是去github上面翻源碼出來看的舒服點,參考[4]
Promise.prototype.then = function (onFulfilled, onRejected) { if (typeof onFulfilled !== 'function' && this.state === FULFILLED || typeof onRejected !== 'function' && this.state === REJECTED) { return this; } var promise = new this.constructor(INTERNAL); if (this.state !== PENDING) { var resolver = this.state === FULFILLED ? onFulfilled : onRejected; unwrap(promise, resolver, this.outcome); } else { this.queue.push(new QueueItem(promise, onFulfilled, onRejected)); } return promise; };
簡單說明:
執行then函數的時候,如果Promise狀態是Fulfilled的話,就執行unwrap那個邏輯,把then函數體通過nextTick放到一個全局的隊列
執行then函數的時候,如果Promise是Pending狀態,就執行 this.queue.push ,看到this就知道這個是放在Promise對象自己的一個隊列里面
分析時序
有了上面的這些理解,接下來分析一下一開始我不理解的打印結果。有些步驟簡單明了,可能被我過了。當然,因為自己第一次去接觸這個,所以步驟會寫的比較繁瑣一點~
- 1.執行Promise的時候,函數體同步執行,直接resolve,所以Promise對象為fulfilled狀態。[理論1,理論2]
- 2.同步執行then1函數的時候,then本身是一個函數,只是它的參數(里面的函數體)沒有執行。這個函數體放在nextTick隊列中[理論5]。then1執行完的時候,同步返回一個匿名的Promise對象[理論3]。並且這個Promise對象的狀態是Pending,因為這個對象相關的then1函數體還沒有執行[理論4]
- 3.同步執行then2。同上,它依賴then1返回的匿名Promise對象狀態。因為是pending,那么then2函數體放在then1返回的匿名Promise對象自己的隊列中
- 4.下一個循環執行nextTick隊列,也就是then1的函數體
- 5.先同步執行一個new的Promise對象函數體,打印’then1 promise’.並且這個Promise對象的狀態被resolve切換為fulfilled
- 6.同步執行then1then1這個.then函數,並且把她的參數(回調函數體)放在nextTick隊列中。並且返回的匿名Promise對象為pending狀態
- 7.同步執行then1then2這個.then函數,因為上面一個then1then1回調函數體還沒有執行,返回一個pending狀態的匿名Promise對象.所以這個then1then2函數回調函數體放在上面的匿名Promise對象的隊列中
- 8.最后,then1函數體沒有返回任何東西,所以根據[理論4],這個Promise的狀態變成了fulfilled,即第一個.then1函數返回的Promise對象的狀態改變了。這個時候其實是執行了resolve函數切換狀態,這個函數里面會看這個Promise對象自己的隊列里面是不是有回調函數,然后把這些放到timeTick隊列里面[理論3]。所以.then2的回調函數體從Promise對象自己的隊列移到了nextTick隊列里面。注意這個時候then1then1和then2的函數體都在tick隊列中,但是都沒有執行。再加上js是單線程,會順序執行tick隊列里面的回調函數體,所以接下來就按順序執行then1then1和then2
- 9.再下一個循環,分別執行then1then1和then2兩回調函數體,所以會先打印‘then1then1’,然后是’then2’
- 10.then1then1回調函數體執行完畢的時候,她的Promise對象也會切換為fulfilled,並且把then1then2回調函數體放在tick隊列中
- 11.再再下一個循環。最后執行then1then2函數體,打印’then1then2’。完美收官
資源搜索網站大全https://55wd.com 廣州品牌設計公司http://www.maiqicn.com
練習
如果例子中的new Promise改成,前面加一個return呢?
.then(() => { // 執行.then的時候生成一個promise是給最后一個.then的 console.log("then1") new Promise((resolve, reject) => { console.log("then1promise") resolve() }) // ===》改成 .then(() => { // 執行.then的時候生成一個promise是給最后一個.then的 console.log("then1") // 就是這里的加了一個return return new Promise((resolve, reject) => { console.log("then1promise") resolve() })
根據[理論4],.then1函數體有了返回值,是一個Promise對象,而且是then1then2執行完返回的匿名Promise對象。所以只有等這個Promise對象resolve了之后,才會執行then2回調函數體的邏輯,所以’then2’最后才會打印。所以最終結果是:
promise then1 then1promise then1then1 then1then2 then2a