[JS] Long promise chain and delay

Scenario

最近的一個爬蟲案子需要定時重複打大量的 API endpoint,每一次的爬取行為內,都包含上千個URL需要爬取。麻煩的是這些動作有其順序性,且參數(query string)是根據外部檔案定義,同時又要避開 Server 端 rate limit,因此筆記一下使用的小技巧。

 

Chaining promises

關於 JS 的 Promise,不斷以 then 來循序執行該做的事本身就是一件非常直覺的事,但是不是每次都能將要執行的code寫出來(例如底下有一千個步驟要做),因此可以用以下方法將外部資料(任何來源,只要讀得懂),變成長長的Promise chain ,用法上跟你寫一個 function 來產生要執行的 Promise 是一樣的。

 

Data

一個 Array of Objects

var source = require('./urls.json');
console.log(source)
// [ {url:'http://aaa.com', delay:2000, something_else: '...'}, ... ]

 

Chaining

為了要照 source 裡的資料來執行,因此把陣列裡的物件串進 Promise chain 裡:

function myChain() {
    return source.reduce( (pre, cur) => {
        return pre.then( () => {
            crawl(cur.url, cur.something_else);
        });
    }, new Promise( (resolve, reject) => {
        // starting
        resolve();
    }));
}

 

Delay

為了避開API rate limit, robot filtering(或其他特殊原因),每個步驟的delay使用了不同數值的毫秒數,為了將 delay 放進每個步驟中,上述函式改成:

function myChain() {
    return source.reduce( (pre, cur) => {
        return pre.then( () => {
            crawl(cur.url, cur.something_else);
        }).then( async () => {
            // apply the delay
            await sleep(cur.interval);
        });
    }, new Promise( (resolve, reject) => {
        // starting
        resolve();
    }));
}

function sleep(time) {
    return new Promise(function (resolve) {
        let i = setTimeout(()=>{
            clearTimeout(i);
            resolve();
        }, time)
    });
}