說明一下,我并非不知道正常的用法,也知道這兩種添加異步代碼的方式是不同的(很多人提到的本輪event loop 和下一輪的問題)。
我希望了解到的是為什么運行結果存在不同的可能,因為延時都是0 ms(按照規(guī)范也就是默認的瀏覽器內置的最小間隔 k ms)。
至于截圖,已補。
代碼如下:
let p = new Promise((res) => {
setTimeout(() => {
res(233)
}, 0)
})
let another = p.then(() => {
let a = 'then 1'
setTimeout(() => {
console.log(a)
}, 0)
}).then(() => {
console.log('then 2')
})
setTimeout(() => {
console.log('time')
}, 0)
// time
// then 2
// then 1
// 或者
// then 2
// time
// then 1
運行環(huán)境:node.js v6.11.2
系統(tǒng):win10 專業(yè)版
在同一次循環(huán)中,Promise是優(yōu)先的,樓主的代碼不是Promise和setTimeout執(zhí)行順序不確定,是Promise里的setTimeout的回調和不被Promise封裝的setTimeout的回調執(zhí)行順序不確定,我的意思是說例子有點問題,事實上在Promise里console.log是肯定會在前面的。
以下為lib/timer源碼的注釋
// ╔════ > Object Map
// ║
// ╠══
// ║ refedLists: { '40': { }, '320': { etc } } (keys of millisecond duration)
// ╚══ ┌─────────┘
// │
// ╔══ │
// ║ TimersList { _idleNext: { }, _idlePrev: (self), _timer: (TimerWrap) }
// ║ ┌────────────────┘
// ║ ╔══ │ ^
// ║ ║ { _idleNext: { }, _idlePrev: { }, _onTimeout: (callback) }
// ║ ║ ┌───────────┘
// ║ ║ │ ^
// ║ ║ { _idleNext: { etc }, _idlePrev: { }, _onTimeout: (callback) }
// ╠══ ╠══
// ║ ║
// ║ ╚════ > Actual JavaScript timeouts
// ║
// ╚════ > Linked List
用人話說就是
// 10/100/1000分別為延遲10/100/1000毫秒后執(zhí)行
refedLists = {
10: item<->item<->item<->item<->item,
100: item<->item<->item<->item<->item,
1000: item<->item<->item<->item<->item
......
}
我的理解是,這個問題其實都是由于例子不愜當引起的。
@代碼宇宙 想說的是timer既然是鏈表,那么順序是不是一定的,答案是是的。
那么為什么執(zhí)行順序不一樣呢,我簡化一下代碼。
let p = new Promise((res) => {
setTimeout(() => {
console.log('promise 1')
//新創(chuàng)建的timer一定在前面
res()
//...其他同步代碼
}, 0)
})
p.then(() => {
//這是promise的回調 并不在timer中 所以如果不是同一輪 出現(xiàn)在哪都有可能
console.log('then 2')
})
setTimeout(() => {
console.log('time')
}, 0)
// promise 1
// time
// then 2
// 或者
// promise 1
// then 2
// time
不知道我有沒有說錯
@toBeTheLight 我還是不太明白,希望與你繼續(xù)討論:
let p = new Promise((res) => {
// 1
setTimeout(() => {
// 2
}, 0)
})
let another = p.then(() => {
// 3
setTimeout(() => {
// 4
}, 0)
}).then(() => {
// 5
})
// 6
setTimeout(() => {
// 7
}, 0)
我們看我標記的這幾處關鍵位置。1是在Promise回調里面,所以屬于同步代碼,最先執(zhí)行,因此將2這個setTimeout任務添加到事件隊列里面。
OK,2、3、4、5先不說,因為又隔了一層或兩層setTimout,肯定要放到第二輪循環(huán)里面了,因此暫不考慮。下面主要看6,因為它是同步代碼,所以應該立即執(zhí)行的,于是把7這個setTimeout任務添加到事件隊列里面去。
現(xiàn)在1和6已執(zhí)行完畢,事件循環(huán)隊列里現(xiàn)在有兩個任務2和7,它們都是setTimeout任務。等到下一輪事件循環(huán),應該依次執(zhí)行,因此是2先執(zhí)行,將該Promise對象resolve,然后是7,打印“time”。
到此為止,7已經(jīng)執(zhí)行完畢了,打印出了time。接下來才是兩個then的執(zhí)行,過程比較簡單,就不分析了。所以最后應該是time->then 2->then 1。而這也跟我實際測試的結果一致。
所以我認為,雖然Node的事件循環(huán)比瀏覽器的要復雜一點,但是這里并不會出現(xiàn)你說的問題。因為每一輪循環(huán)的執(zhí)行順序都具有明確的優(yōu)先級,不會有不確定的事情發(fā)生。
看阮一峰的博客,他舉了一個會出現(xiàn)不確定性的例子:
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
這個例子可能會打印1,2,也可能是2,1,這是可以理解的,因為setTimeout和setImmediate是屬于同一級別的異步任務,但是在一輪循環(huán)里面的位置不同,而定時器的0秒并不是真正的0,所以孰先孰后要看運氣。但是題主這個程序,執(zhí)行順序應該是確定一定以及肯定的才對。
以上。如有問題歡迎指出。
promise和setTimeout是有順序的,分了macro-task 機制和 micro-task 機制,promise永遠比setTimeout先調用
以下是按我個人理解精簡過的你的代碼
setTimeout(function () {
console.log('time 1');
Promise.resolve().then(function () {
console.log('then 1');
});
}, 100);
setTimeout(function () {
console.log('time 2');
}, 100);
//time 1 time 2 then 1
//or
//time 1 then 1 time 2
問題就是 兩個定期器有時是同時執(zhí)行有時是分別執(zhí)行,也就是說node每次去取的并不是一個定時器
再看下面代碼
for (var i = 0; i < 100; i++) {
setTimeout(function (i) {
console.log('time %d', i);
Promise.resolve().then(function () {
console.log('then %d', i);
});
}, 0, i);
}
我之前以為是每次去異步隊列里面取一個放入主線程調用然后在觸發(fā)下一次輪詢,也就是time 0 then 0 time 1 then 1 ... time 99 then 99 ,實際上瀏覽器是這樣的,但是在node中不是
上例中大部分請求都是 time 0~99 then 0~99
但偶爾有特殊請求 比如 time 0 then 0 time 1~99 then 1~99或者 time 0~46 then 0~46 time 47~99 then 47~99或者其他情況
也就是說node是在批量調用異步隊列的時候不一定是一次取完,像上例偶爾會分兩次
其實直接拿setTimeout0做測試是不嚴謹?shù)模?的時間是不能確定的,js代碼每次執(zhí)行花費的時間也是不確定的。有setTimeout0的時候,我一般會在這一層的最后加一個
function sleep(time) {
let startTime = new Date()
while (new Date() - startTime < time) {}
console.log('1s over')
}
確保在同步代碼執(zhí)行完畢后,setTimeout確實被觸發(fā)了。
順便糾正一個別的答案的關于then的連續(xù)調用的問題。then中沒有返回值時,會自動創(chuàng)建一個promise,并且終態(tài)為調用then的promise的終態(tài),也就是.then().then().then().....可以一直這么寫下去。
當前分析僅針對當前正式版本node 8.x,較低版本和未來版本不做保證。
先假設你已經(jīng)了解了,node中事件循環(huán)的幾個階段,如果不了解的話可以看看瀏覽器和Node不同的事件循環(huán)(Event Loop),我們以這幾個階段為基礎說下分析。
摘要:
let p = new Promise((res) => {
setTimeout(() => {
console.log('timeout-promise')
res()
}, 0)
})
let another = p.then(() => {
// then1
setTimeout(() => {
console.log('timeout-then')
}, 0)
}).then(() => {
// then2
console.log('then.then')
})
以確確實實開始執(zhí)行的事件循環(huán)作為列舉出的循環(huán)(還會有別的沒有執(zhí)行但是在等待的循環(huán),會因為第一個timer的到來兒結束,進入下輪循環(huán))
第一輪循環(huán)
timeout-promise此時已經(jīng)觸發(fā),已被加入Timers Queue,執(zhí)行輸出timeout-promise,then1加入Microtask Queuetimeout-then被創(chuàng)建,then2加入Microtask Queue,繼續(xù)清空Microtask Queue,輸出then.then
第二輪循環(huán)
timeout-then已經(jīng)觸發(fā),已被加入Timers Queue,在timer階段,被執(zhí)行,輸出timeout-then。所以輸出是:// timeout-promise// then.then <-第一輪// timeout-then <-第二輪
可以保證的是這三個順序是確定的。
let p = new Promise((res) => {
setTimeout(() => {
console.log('timeout-promise')
res()
}, 0)
})
let another = p.then(() => {
setTimeout(() => {
// timeout2
console.log('timeout-then')
}, 0)
})
setTimeout(() => {
console.log('timeout-out')
}, 0)
按照node的事件循環(huán)機制,我們依然以確確實實開始執(zhí)行的事件循環(huán)作為列舉出的循環(huán)。
由于setTimeout的0延遲實際肯定不為0,js代碼每次執(zhí)行花費的時間也是不確定的,這兩種不確定性,導致了我們不知道setTimeout被添加到Timers Queue隊列的具體時刻:
timeout-promise的,那么這個時候Timers Queue里的后面有沒有timeout-out呢,可能前面的代碼比較慢,已經(jīng)觸發(fā),所以被加進來了?timeout-then的,那么timeout-out是在第一輪的隊列里還是這這個的隊列里的最前面呢,可能前面的代碼比較快,導致第二輪循環(huán)之前才被觸發(fā),才被加進來?不管怎么樣,我們可以確定的是,三個timeout的輸出順序是://timeout-promise <-第一輪// timeout-out <-第?輪// timeout-then <-第二輪
所以我才講timeout之間的執(zhí)行先后順序是百分百確定的。
結合上面的兩種情況,有兩個順序是確定的:// timeout-promise// then.then <-第一輪// timeout-then <-第二輪//timeout-promise <-第一輪// timeout-out <-第?輪// timeout-then <-第二輪
把你的兩種結果拆開來看,也是符合這個順序的。timeout-out的位置的不確定才導致了出現(xiàn)了兩種情況。
所以我才講需要加一個sleep函數(shù),來確保,同步代碼完成后,已經(jīng)創(chuàng)建的兩個setTimeout0都已經(jīng)被觸發(fā),被加入到了第一輪循環(huán)的Timers Queue中。
一個setTimeout和一個setImmediate
我們知道在同一輪循環(huán)中,setTimeout執(zhí)行的階段比setImmediate執(zhí)行的階段要靠前,但是這段代碼
setTimeout(()=>{console.log('out')}, 0)
setImmediate(()=>{console.log('Immediate')})
基本上有對半的幾率是先Immediate,這就是因為setTimeout在第二輪循環(huán)才被加入了Timer Queue隊列中,有兩個解決辦法:
很多setTimeout和一個setImmediate
for(let i = 0; i < 20; i++) {
setTimeout(()=>console.log(i), 0);
}
setImmediate(() => {console.log('我是第一輪')})
你可以看到這個我是第一輪可能會出現(xiàn)在任意位置。
可以參考下阮一峰老師的 Node 定時器詳解
1.本輪循環(huán)一定早于次輪循環(huán)執(zhí)行
2.Node 規(guī)定,process.nextTick和Promise的回調函數(shù),追加在本輪循環(huán),即同步任務一旦執(zhí)行完成,就開始執(zhí)行它們。而setTimeout、setInterval、setImmediate的回調函數(shù),追加在次輪循環(huán)。
嗯,應該是用法不對!Promise里的異步需要加上res/rej一起,順序才能達到你想要的:
function p1(){
return new Promise((res,rej) => {
let flag = Math.random()*10;
setTimeout(() => {
if(flag > 1){
console.log('p1')
res('success p1');
}else {
rej('fails p1');
}
},1000);
});
}
function p2(){
return new Promise((res,rej) => {
let flag = Math.random()*10;
setTimeout(() => {
if(flag > 1){
console.log('p2');
res('success p2');
}else {
rej('fails p2');
}
},1000);
});
}
let p = new Promise((res,rej) => {
let flag = Math.random()*10;
setTimeout(() => {
if(flag > 1){
console.log('p');
res('success p');
}else {
rej('fails p');
}
},1000);
})
p.then(p1).then(p2).then((str) => {
console.log(str);
}).catch((str)=>{
console.log(str);
})
多步then的話,前面的then應該返回Promise的,不然后面就不要再then了
因為promise和setTimeout都是異步的,當然就沒有固定的執(zhí)行順序。
但promise中的then一定是先執(zhí)行前一個then的“同步”部分的代碼,再執(zhí)行下一個then的同步部分的代碼。
比如,樓主的代碼如果改成:
let p = new Promise((res) => {
setTimeout(() => {
res(233)
}, 0)
})
let another = p.then(() => {
let a = 'then 1'
console.log(a);
// setTimeout(() => {
// console.log(a)
// }, 0)
}).then(() => {
console.log('then 2')
})
setTimeout(() => {
console.log('time')
}, 0)
則 then1 一定會出現(xiàn)在 then2 之前。
瀏覽器跟 node 的 event loop 是不一樣的,因為它們處理的領域不一樣。
在 whatwg 定義中,每處理完一個 task (從任意 task 隊列中取最舊的)就要清空 micro tasks 隊列,這導致了 then2 比 time 要早。
而 node 中,到期的 timers 是在 poll 階段一并處理。而 micro task 隊列是在 next tick 隊列之后才處理,也就是要等當前階段(poll)的 tasks 處理完畢。所以 p 產生的 micro tasks 是在 time 之后才被處理。
可以這么觀察
var p = new Promise((resolve) => {
console.log('233 started')
setTimeout(() => {
resolve(console.log('233 resolved'))
}, 0)
})
var another = p.then(() => {
console.log('then1 started')
setTimeout(() => {
console.log('then1 resolved')
}, 0)
}).then(() => {
console.log('then2 resolved')
})
console.log('timer started')
setTimeout(() => {
console.log('timer resolved')
}, 0)北大青鳥APTECH成立于1999年。依托北京大學優(yōu)質雄厚的教育資源和背景,秉承“教育改變生活”的發(fā)展理念,致力于培養(yǎng)中國IT技能型緊缺人才,是大數(shù)據(jù)專業(yè)的國家
達內教育集團成立于2002年,是一家由留學海歸創(chuàng)辦的高端職業(yè)教育培訓機構,是中國一站式人才培養(yǎng)平臺、一站式人才輸送平臺。2014年4月3日在美國成功上市,融資1
北大課工場是北京大學校辦產業(yè)為響應國家深化產教融合/校企合作的政策,積極推進“中國制造2025”,實現(xiàn)中華民族偉大復興的升級產業(yè)鏈。利用北京大學優(yōu)質教育資源及背
博為峰,中國職業(yè)人才培訓領域的先行者
曾工作于聯(lián)想擔任系統(tǒng)開發(fā)工程師,曾在博彥科技股份有限公司擔任項目經(jīng)理從事移動互聯(lián)網(wǎng)管理及研發(fā)工作,曾創(chuàng)辦藍懿科技有限責任公司從事總經(jīng)理職務負責iOS教學及管理工作。
浪潮集團項目經(jīng)理。精通Java與.NET 技術, 熟練的跨平臺面向對象開發(fā)經(jīng)驗,技術功底深厚。 授課風格 授課風格清新自然、條理清晰、主次分明、重點難點突出、引人入勝。
精通HTML5和CSS3;Javascript及主流js庫,具有快速界面開發(fā)的能力,對瀏覽器兼容性、前端性能優(yōu)化等有深入理解。精通網(wǎng)頁制作和網(wǎng)頁游戲開發(fā)。
具有10 年的Java 企業(yè)應用開發(fā)經(jīng)驗。曾經(jīng)歷任德國Software AG 技術顧問,美國Dachieve 系統(tǒng)架構師,美國AngelEngineers Inc. 系統(tǒng)架構師。