JS實現非同步timeout

aokihu發表於2019-02-27

Promise已經作為JS的ES6標準,已經正式進入了瀏覽器環境和Node.js環境,但是對於程式設計師的需求來說,現在的Promise的功能還是有點不足的,比如說,我要設定一組Timeout該怎麼做呢?

傳統的同步方法實現起來很簡單

let count = 10;
const func = () => {
    if(count > 0) {
        setTimeout(func, 1000);
        count -= 1;
    }
}
複製程式碼

這樣我們就能實現10次,每次1秒的定時設定了,是不是很簡單。

但是現在的問題是要用非同步的方式來實現,那麼繼續使用原來的方法看看把

let count = 10;

const func = async () => {
    if(count > 0) {
        setTimeout(func, 1000);
        count -= 1;
    }
}
複製程式碼

唔…看起來似乎也沒有問題,真的沒有問題嗎?請仔細的想想看func的型別,在同步方法中func的型別是Function,但是在非同步方法中也是Function嗎?NO!並不是,在非同步方法中func的型別是Promise!OK,那麼問題來了,既然func的型別是Promise那麼你怎麼可能用setTimeout(function, timeout)這個函式呢?你傳遞的型別都不多,結果自然是無法執行了!

那麼有什麼方法可以是實現非同步的定時呼叫呢?畢竟有時候我們會需要一些網路傳輸的測試,當網路狀況不良的時候,希望通過定時測試網路連線,獲取網路狀況,這裡就要用到Promise.race()這個方法了,你或許說會文為什麼不用bluebird庫,裡面也有實現的方法,這裡我想提醒的是Promise.race()這個方法是目前在瀏覽器端和Node.js端中都是原生提供的方法,實現起來的移植性更高,那麼現在我們來假想一個使用場景,現在我們需要連線遠端的伺服器,但是並不能保證100%的成功率,因此需要去ping一下伺服器看看伺服器是不是還活著

const axios = require(`axios`);

// 最大的連線重試次數
const MAX_RETRYS = 10
let count = 0;

/**
 * 測試伺服器是不是還活著
 * @method {Function} resolve Promise的成功呼叫方法
 * @method {Function} reject Promise的失敗呼叫方法
 */
const ping = async (resolve, reject) => {
    const response = axios.get(...);
    if(response.status === 200) {resolve();}
    else if(count > MAX_RETRYS) {reject();}
    
    count += 1;
}

// 以下是非同步定時呼叫方法
// 每隔1秒重新ping一次
const tasks = [...Array(MAX_RETRYS + 1).keys()].map(i => {
    return new Promise((resolve,reject) => {
        setTimeout(ping(resolve, reject), i * 1000)
    })
})

// 處理ping通的
tasks.race().then(() => {.../*成功的後續執行*/})
.catch(() => {.../*失敗的後續執行*/})
複製程式碼

這裡我使用的方法是通過產生一組任務來模擬時間間隔,可以這麼理解,我在時刻0的時候去ping一下,時刻1秒的時候去ping一下,時刻2秒的時候去ping一下,以此類推產生一組ping的任務,當時刻到的時候才去執行,效果跟同步的方法差不多,但是有一個與同步方法特別的不同的地方,就是這個任務佇列不能是無窮大的,因此要設定一個最大嘗試次數,其實實際應用時我們也會設定這麼一個限制值,總不可能使用者等一個小時連線伺服器吧

對了這裡解釋下[...Array(MAX_RETRYS+1).keys()]的程式碼含義,這個方法類似於python中的range()函式,但是Javascript沒有原生的實現方法,因此這裡使用了ES6的新特性,簡單的模擬了range()函式,具體含義大家看官方文件吧,很簡單的

希望我的這個方法對你有幫助

相關文章