在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 問答/HTML/ 為什么 promise 和 setTimeout 執(zhí)行時序不確定?

為什么 promise 和 setTimeout 執(zhí)行時序不確定?

說明一下,我并非不知道正常的用法,也知道這兩種添加異步代碼的方式是不同的(很多人提到的本輪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)先的,樓主的代碼不是PromisesetTimeout執(zhí)行順序不確定,是Promise里的setTimeout的回調和不被Promise封裝的setTimeout的回調執(zhí)行順序不確定,我的意思是說例子有點問題,事實上在Promiseconsole.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

不知道我有沒有說錯

2018年5月28日 01:07
編輯回答
尐潴豬

@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,這是可以理解的,因為setTimeoutsetImmediate是屬于同一級別的異步任務,但是在一輪循環(huán)里面的位置不同,而定時器的0秒并不是真正的0,所以孰先孰后要看運氣。但是題主這個程序,執(zhí)行順序應該是確定一定以及肯定的才對。

以上。如有問題歡迎指出。

2018年2月27日 00:39
編輯回答
六扇門

promisesetTimeout是有順序的,分了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是在批量調用異步隊列的時候不一定是一次取完,像上例偶爾會分兩次

2018年3月20日 17:18
編輯回答
護她命

其實直接拿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),我們以這幾個階段為基礎說下分析。

摘要:

  • new Promise本身的內部代碼并不是異步任務,其resolve和reject觸發(fā)的then和catch才是。
  • node端的事件循環(huán)是分階段的。

我們先刪去引起問題的timeout,看一下其他的流程

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))

  1. 同步代碼
  2. 第一輪循環(huán)

    • 在timer階段,timeout-promise此時已經(jīng)觸發(fā),已被加入Timers Queue,執(zhí)行輸出timeout-promise,then1加入Microtask Queue
    • 執(zhí)行Microtask Queue,timeout-then被創(chuàng)建,then2加入Microtask Queue,繼續(xù)清空Microtask Queue,輸出then.then
  3. 第二輪循環(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隊列的具體時刻:

  1. 第一輪循環(huán),Timers Queue里是肯定有timeout-promise的,那么這個時候Timers Queue里的后面有沒有timeout-out呢,可能前面的代碼比較慢,已經(jīng)觸發(fā),所以被加進來了?
  2. 第二輪循環(huán),Timers Queue里肯定是有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中。

其他示例

  1. 一個setTimeout和一個setImmediate
    我們知道在同一輪循環(huán)中,setTimeout執(zhí)行的階段比setImmediate執(zhí)行的階段要靠前,但是這段代碼

    setTimeout(()=>{console.log('out')}, 0)
    setImmediate(()=>{console.log('Immediate')})

    基本上有對半的幾率是先Immediate,這就是因為setTimeout在第二輪循環(huán)才被加入了Timer Queue隊列中,有兩個解決辦法:

    1. 可以在最后加上長時間的sleep,使進入循環(huán)時,確保兩個事件都被觸發(fā),都在第一輪循環(huán),這個很靠譜。
    2. 甚至可以在后面多寫幾行代碼,也能一定程度上確保兩個事件都被觸發(fā),這個方法就像setTimeout的0一樣不太靠譜。
  2. 很多setTimeout和一個setImmediate

    for(let i = 0; i < 20; i++) {
      setTimeout(()=>console.log(i), 0);
    }
    setImmediate(() => {console.log('我是第一輪')})

    你可以看到這個我是第一輪可能會出現(xiàn)在任意位置。
    immediate位置

2017年12月11日 02:00
編輯回答
不討喜

可以參考下阮一峰老師的 Node 定時器詳解

1.本輪循環(huán)一定早于次輪循環(huán)執(zhí)行
2.Node 規(guī)定,process.nextTick和Promise的回調函數(shù),追加在本輪循環(huán),即同步任務一旦執(zhí)行完成,就開始執(zhí)行它們。而setTimeout、setInterval、setImmediate的回調函數(shù),追加在次輪循環(huán)。

2018年8月30日 05:31
編輯回答
不將就

嗯,應該是用法不對!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了

2017年8月12日 01:56
編輯回答
涼薄

因為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 之前。

2017年6月2日 04:14
編輯回答
吢丕

瀏覽器跟 node 的 event loop 是不一樣的,因為它們處理的領域不一樣。

whatwg 定義中,每處理完一個 task (從任意 task 隊列中取最舊的)就要清空 micro tasks 隊列,這導致了 then2time 要早。

而 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)
2018年6月9日 03:28