成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

編寫(xiě)高質(zhì)量JavaScript代碼之并發(fā)

SwordFly / 924人閱讀

摘要:實(shí)際上,系統(tǒng)維護(hù)了一個(gè)按事件發(fā)生順序排列的內(nèi)部事件隊(duì)列,一次調(diào)用一個(gè)已注冊(cè)的回調(diào)函數(shù)。提示異步使用回調(diào)函數(shù)來(lái)延緩處理代價(jià)高昂的操作以避免阻塞主應(yīng)用程序。這具有幾乎立刻將回調(diào)函數(shù)添加到事件隊(duì)列上的作用。

參考書(shū)籍:《Effective JavaScript》

并發(fā)

在JavaScript中,編寫(xiě)響應(yīng)多個(gè)并發(fā)事件的程序的方法非常人性化,而且強(qiáng)大,因?yàn)樗褂昧艘粋€(gè)簡(jiǎn)單的執(zhí)行模型(有時(shí)稱為事件隊(duì)列或事件循環(huán)并發(fā))和被稱為異步的API。

不要阻塞I/O事件隊(duì)列

在一些語(yǔ)言中,我們會(huì)習(xí)慣性地編寫(xiě)代碼來(lái)等待某個(gè)特定的輸入。

var text = downloadSync("http://example.com/file.txt");
console.log(text);

形如downloadSync這樣的函數(shù)被稱為同步函數(shù)(或阻塞函數(shù))。程序會(huì)停止做任何工作,而等待它的輸入。在這個(gè)例子中,也就是等待從網(wǎng)絡(luò)下載文件的結(jié)果。由于在等待下載完成的期間,計(jì)算機(jī)可以做其他有用的工作,因此這樣的語(yǔ)言通常為程序員提供一種方法來(lái)創(chuàng)建多個(gè)線程,即并行執(zhí)行自己算。它允許程序的一部分停下來(lái)等待(阻塞)一個(gè)低速的輸入,而程序的另一部分可以繼續(xù)進(jìn)行獨(dú)立的工作。

在JavaScript中,大多的I/O操作都提供了異步的或非阻塞的API。

downloadAsync("http://example.com/file.txt", function (text) {
    console.log(text);
});

該API初始化下載進(jìn)程,然后在內(nèi)部注冊(cè)表中存儲(chǔ)了回調(diào)函數(shù)后立刻返回,而不是被網(wǎng)絡(luò)請(qǐng)求阻塞。

JavaScript有時(shí)被稱為提供一個(gè)運(yùn)行到完成機(jī)制(run-to-completion)的擔(dān)保。任何當(dāng)前正在運(yùn)行于共享上下文的用戶代碼,比如瀏覽器中的單個(gè)Web頁(yè)面或者單個(gè)運(yùn)行的Web服務(wù)器實(shí)例,只有在執(zhí)行完成后才能調(diào)用下一個(gè)事件處理程序。實(shí)際上,系統(tǒng)維護(hù)了一個(gè)按事件發(fā)生順序排列的內(nèi)部事件隊(duì)列,一次調(diào)用一個(gè)已注冊(cè)的回調(diào)函數(shù)。

以客戶端(mouse moved、file downloaded)和服務(wù)器端(file read、path resolved)應(yīng)用程序事件為例,隨著事件的發(fā)生,它們被添加到應(yīng)用程序的事件隊(duì)列的末尾。JavaScript系統(tǒng)使用一個(gè)內(nèi)部循環(huán)機(jī)制來(lái)執(zhí)行應(yīng)用程序。該循環(huán)機(jī)制每次都拉取隊(duì)列底部的事件,也就是說(shuō),以接收到這些事件的順序來(lái)調(diào)用這些已注冊(cè)的JavaScript事件處理程序,并將事件的數(shù)據(jù)作為改事件處理程序的參數(shù)。

運(yùn)行到完成機(jī)制擔(dān)保的好處是當(dāng)代碼運(yùn)行時(shí),你完全掌握應(yīng)用程序的狀態(tài)。你根本不必?fù)?dān)心一些變量和對(duì)象屬性的改變由于并發(fā)執(zhí)行代碼而超出你的控制。并發(fā)編程在JavaScript中往往比使用線程和鎖的C++、Java或C#要容易得多。

然而,運(yùn)行到完成機(jī)制的不足是,實(shí)際上所有你編寫(xiě)的代碼支撐著余下應(yīng)用程序的繼續(xù)執(zhí)行。

JavaScript并發(fā)的一個(gè)最重要的規(guī)則是絕不要在應(yīng)用程序事件隊(duì)列中使用阻塞I/O的API。

相比之下,異步的API用在基于事件的環(huán)境中是安全的,因?yàn)樗鼈兤仁箲?yīng)用程序邏輯在一個(gè)獨(dú)立的事件循環(huán)“輪詢”中繼續(xù)處理。

提示:

異步APi使用回調(diào)函數(shù)來(lái)延緩處理代價(jià)高昂的操作以避免阻塞主應(yīng)用程序。

JavaScript并發(fā)地接收事件,但會(huì)使用一個(gè)事件隊(duì)列按序地處理事件處理程序。

在應(yīng)用程序事件隊(duì)列中絕不要使用阻塞的I/O。

在異步序列中使用嵌套或命名的回調(diào)函數(shù)

理解操作序列的最簡(jiǎn)單的方式是異步API是發(fā)起操作而不是執(zhí)行操作。異步操作完成后,在事件循環(huán)的某個(gè)多帶帶的輪次中,被注冊(cè)的事件處理程序才會(huì)執(zhí)行。

如果你需要在發(fā)起一個(gè)操作后做一些事情,如何串聯(lián)已完成的異步操作。

最簡(jiǎn)單的答案是使用嵌套。

db.lookupAsyc("url", function(url) {
    downloadAsyc(url, function(text) {
        console.log("contents of " + url + ": " + text);
    });
});

嵌套的異步操作很容易,但當(dāng)擴(kuò)展到更長(zhǎng)的序列時(shí)會(huì)很快變得笨拙。

db.lookupAsync("url", function(url) {
    downloadAsync(url, function(file) {
        downloadAsync("a.txt", function(a) {
            downladAsync("b.txt", function(b) {
                downloadAsync("c.txt", function(c) {
                    // ...
                });
            })
        });
    });
});

減少過(guò)多嵌套的方法之一是將嵌套的回調(diào)函數(shù)作為命名的函數(shù),并將它們需要的附加數(shù)據(jù)作為額外的參數(shù)傳遞。

db.lookupAsync("url", downloadURL);

function downloadURL(url) {
    downloadAsync(url, function(text) { // still nested
        showContents(url, text);
    });
}

function showContents(url, text) {
    console.log("contents of " + url + ": " + text);
}

上述代碼仍然使用了嵌套的回調(diào)函數(shù),可以使用bind方法消除最深層的嵌套回調(diào)函數(shù)。

db.lookupAsync("url", downloadURL);

function downloadURL(url) {
    downloadAsync(url, showContents.bind(null, url)); // => window.showContents(url) = function(url, text) { ... } 
}

function showContents(url, text) {
    console.log("contents of " + url + ": " + text);
}   

這種做法導(dǎo)致了代碼看起來(lái)根據(jù)順序性,但需要為操作序列的每個(gè)中間步驟命名,并且一步步地使用綁定,這可能導(dǎo)致尷尬的情況。

更勝一籌的方法是使用一個(gè)額外的抽象來(lái)簡(jiǎn)化。

function downloadFiles(url, file) {
    downloadAllAsync(["a.txt", "b.txt", "c.txt"], function(all) {
        var a = all[0],
            b = all[1],
            c = all[2];
        
        // ...
    });
}

提示:

使用嵌套或命名的回調(diào)函數(shù)按順序地執(zhí)行多個(gè)異步操作。

嘗試在過(guò)多的嵌套的回調(diào)函數(shù)和尷尬的命名的非嵌套回調(diào)函數(shù)之間取得平衡。

避免將可被并行執(zhí)行的操作順序化。

當(dāng)心丟棄錯(cuò)誤

對(duì)于同步的代碼,通過(guò)使用try語(yǔ)句塊包裝一段代碼很容易一下子處理所有的錯(cuò)誤。

try {
    f();
    g();
    h();
} catch (e) {
    // handle any error that occurred...
}

異步的API傾向于將錯(cuò)誤表示為回調(diào)函數(shù)的特定參數(shù),或使用一個(gè)附加的錯(cuò)誤處理回調(diào)函數(shù)(有時(shí)被稱為errbacks)。

downloadAsync("a.txt", function(a) {
    downloadAsync("b.txt", function(b) {
        downloadAsync("c.txt", function(c) {
            console.log("Content: " + a + b + c);   
        }, function(error) {
            console.log("Error: " + error);
        })
    }, function(error) { // repeated error-handling logic
        console.log("Error: " + error);
    })
}, function(error) { // repeated error-handling logic
    console.log("Error: " + error);
})

上述代碼中,每一步的處理都使用了相同的錯(cuò)誤處理邏輯,我們可以在一個(gè)共享的作用域中定義一個(gè)錯(cuò)誤處理的函數(shù),將重復(fù)代碼抽象出來(lái)。

function onError(error) {
    console.log("Error: " + error);
}

downloadAsync("a.txt", function(a) {
    downloadAsync("b.txt", function(b) {
        downloadAsync("c.txt", function(c) {
            console.log("Content: " + a + b + c);   
        }, onError)
    }, onError)
}, onError)

另一種錯(cuò)誤處理API的風(fēng)格只需要一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)的第一個(gè)參數(shù)如果有錯(cuò)誤發(fā)生那就表示為一個(gè)錯(cuò)誤,否則就位一個(gè)假值,比如null。

function onError(error) {
    console.log("Error: " + error);
}

downloadAsync("a.txt", function(error, a) {
    if (error) return onError(error);

    downloadAsync("b.txt", function(error, b) {
        if (error) return onError(error);

        downloadAsync(url13, function(error, c) {
            if (error) return onError(error);

            console.log("Content: " + a + b + c);
        });
    });
});

提示:

通過(guò)編寫(xiě)共享的錯(cuò)誤處理函數(shù)來(lái)避免復(fù)制和粘貼錯(cuò)誤處理代碼。

確保明確地處理所有的錯(cuò)誤條件以避免丟棄錯(cuò)誤。

對(duì)異步循環(huán)使用遞歸

設(shè)想有一個(gè)函數(shù)接收一個(gè)URL的數(shù)組并嘗試依次下載每個(gè)文件。

function downloadOneSync(urls) {
    for (var i = 0, n = urls.length; i < n; i++) {
        downloadAsync(urls[i], onsuccess, function(error) {
            // ?
        });

        // loop continues
    }

    throw new Error("all downloads failed");
}

上述代碼將啟動(dòng)所有的下載,而不是等待一個(gè)完成再試下一個(gè)。

解決方案是將循環(huán)實(shí)現(xiàn)為一個(gè)函數(shù),所以我們可以決定何時(shí)開(kāi)始每次迭代。

function downloadOneAsync(urls, onsuccess, onfailure) {
    var n = urls.length;

    function tryNextURL(i) {
        if (i >= n) {
            onfailure("all downloads failed");
            return;
        }

        downloadAsync(urls[i], onsuccess, function() {
            tryNextURL(i + 1);
        });
    }

    tryNextURL(0);
}

局部函數(shù)tryNextURL是一個(gè)遞歸函數(shù)。它的實(shí)現(xiàn)調(diào)用了其自身。目前典型的JavaScript環(huán)境中一個(gè)遞歸函數(shù)同步調(diào)用自身過(guò)多次(例如10萬(wàn)次)會(huì)導(dǎo)致失敗。

JavaScript環(huán)境通常在內(nèi)存中保存一塊固定的區(qū)域,稱為調(diào)用棧,用于記錄函數(shù)調(diào)用返回前下一步該做什么。

function negative(x) {
    return abs(x) * -1;
}

function abs(x) {
    return Math.abs(x);
}

console.log(negative(42));

當(dāng)程序使用參數(shù)42調(diào)用Math.abs方法時(shí),有好幾個(gè)其他的函數(shù)調(diào)用也在進(jìn)行,每個(gè)都在等待另一個(gè)的調(diào)用返回。

最新的函數(shù)調(diào)用將信息推入棧(被表示為棧的最底層的幀),該信息也將首先從棧中彈出。當(dāng)Math.abs執(zhí)行完畢,將會(huì)返回給abs函數(shù),其將返回給negative函數(shù),然后將返回到最外面的腳本。

當(dāng)一個(gè)程序執(zhí)行中有太多的函數(shù)調(diào)用,它會(huì)耗盡??臻g,最終拋出異常,這種情況被稱為棧溢出。

downloadOneAsync函數(shù),不是直到遞歸調(diào)用返回后才被返回,downloadOneAsync只在異步回調(diào)函數(shù)中調(diào)用自身。記住異步API在其回調(diào)函數(shù)被調(diào)用前會(huì)立即返回。所以downloadOneAsync返回,導(dǎo)致其棧幀在任何遞歸調(diào)用將新的棧幀推入棧前,會(huì)從調(diào)用棧中彈出。事實(shí)上,回調(diào)函數(shù)總在事件循環(huán)的多帶帶輪次中被調(diào)用,事件循環(huán)的每個(gè)輪次中調(diào)用其事件處理程序的調(diào)用棧最初是空的。所以無(wú)論downloadOneAsync需要多少次迭代,都不會(huì)耗盡??臻g。

提示:

循環(huán)不能是異步的。

使用遞歸函數(shù)在事件循環(huán)的多帶帶輪次中執(zhí)行迭代。

在事件循環(huán)的多帶帶輪次中執(zhí)行遞歸,并不會(huì)導(dǎo)致調(diào)用棧溢出。

不要在計(jì)算時(shí)阻塞事件隊(duì)列

如果你的應(yīng)用程序需要執(zhí)行代價(jià)高昂的算法你該怎么辦呢?

也許最簡(jiǎn)單的方法是使用像Web客戶端平臺(tái)的Worker API這樣的并發(fā)機(jī)制。

但是不是所有的JavaScript平臺(tái)都提供了類似Worker這樣的API,另一種方法是算法分解為多個(gè)步驟,每個(gè)步驟組成一個(gè)可管理的工作塊。

Member.prototype.inNetwork = function(other){
    var visited = {},
        worklist = [this];

    while (worklist.length > 0) {
        var member = worklist.pop();

        // ...

        if (member === other) { // found?
            return true;
        }

        // ...
    }

    return false;
};

如果這段程序核心的while循環(huán)代價(jià)太過(guò)高昂,搜索工作很可能會(huì)以不可接受的時(shí)間運(yùn)行而阻塞應(yīng)用程序事件隊(duì)列。

幸運(yùn)的是,這種算法被定義為一個(gè)步驟集的序列——while循環(huán)的迭代。我們可以通過(guò)增加一個(gè)回調(diào)參數(shù)將inNetwork轉(zhuǎn)換為一個(gè)匿名函數(shù),將while循環(huán)替換為一個(gè)匿名的遞歸函數(shù)。

Member.prototype.inNetwork = function(other, callback) {
    var visited = {},
        worklist = [this];

    function next() {
        if (worklist.length === 0) {
            callback(false);
            return;
        }

        var number = worklist.pop();

        // ...

        if (member === other) { // found?
            callback(true);
            return;
        }

        // ...

        setTimeout(next, 0); // schedule the next iteration
    }

    setTimeout(next, 0); // schedule the next iteration
};

局部的next函數(shù)執(zhí)行循環(huán)中的單個(gè)迭代然后調(diào)度應(yīng)用程序事件隊(duì)列來(lái)異步運(yùn)行下一次迭代。這使得在此期間已經(jīng)發(fā)生的其他事件被處理后才繼續(xù)下一次迭代。當(dāng)搜索完成后,通過(guò)找到一個(gè)匹配或遍歷完整個(gè)工作表,我們使用結(jié)果值調(diào)用回調(diào)函數(shù)并通過(guò)調(diào)用沒(méi)有調(diào)度任何迭代的next來(lái)返回,從而有效地完成循環(huán)。

要調(diào)度迭代,我們使用多數(shù)JavaScript平臺(tái)都可用的、通用的setTimeout API來(lái)注冊(cè)next函數(shù),是next函數(shù)經(jīng)過(guò)一段最少時(shí)間(0毫秒)后運(yùn)行。這具有幾乎立刻將回調(diào)函數(shù)添加到事件隊(duì)列上的作用。

提示:

避免在主事件隊(duì)列中執(zhí)行代價(jià)高昂的算法。

在支持Worker API的平臺(tái),該API可以用來(lái)在一個(gè)獨(dú)立的事件隊(duì)列中運(yùn)行長(zhǎng)計(jì)算程序。

在Worker API不可用或代價(jià)昂貴的環(huán)境中,考慮將計(jì)算程序分解到事件循環(huán)的多個(gè)輪次中。

使用計(jì)數(shù)器來(lái)執(zhí)行并行操作
function downloadAllAsync(urls, onsuccess, onerror) {
    var result = [],
        length = urls.length;

    if (length === 0) {
        setTimeout(onsuccess.bind(null, result), 0);
        return;
    }

    urls.forEach(function(url) {
        downloadAsync(url, function(text) {
            if (result) {
                // race condition
                reuslt.push(text);

                if (result.length === urls.length) {
                    onsuccess(result);
                }
            }
        }, function(error) {
            if (result) {
                result = null;
                onerror(error);
            }
        });
    });
}

上述代碼有錯(cuò)誤。

當(dāng)一個(gè)應(yīng)用程序依賴于特定的事件順序才能正常工作時(shí),這個(gè)程序會(huì)遭受數(shù)據(jù)競(jìng)爭(zhēng)(data race)。數(shù)據(jù)競(jìng)爭(zhēng)是指多個(gè)并發(fā)操作可以修改共享的數(shù)據(jù)結(jié)構(gòu),這取決于它們發(fā)生的順序。

var filenames = [
    "huge.txt",
    "tiny.txt",
    "medium.txt"
];

downloadAllAsync(filenames, function(files) {
    console.log("Huge file: " + files[0].length); // tiny
    console.log("Tiny file: " + files[1].length); // medium
    console.log("Medium file: " + files[2].length); // huge
}, function(error) {
    console.log("Error: " + error);
});

由于這些文件是并行下載的,事件可以以任意的順序發(fā)生。例如,如果tiny.txt先下載完成,接下來(lái)是medium.txt文件,最后是huge.txt文件,則注冊(cè)到downloadAllAsync的回調(diào)函數(shù)并不會(huì)按照它們被創(chuàng)建的順序進(jìn)行調(diào)用。但downloadAllAsync的實(shí)現(xiàn)是一旦下載完成就立即將中間結(jié)果保存在result數(shù)組的末尾。所以downloadAllAsync函數(shù)提供的保存下載文件內(nèi)容的數(shù)組的順序是未知的。

下面的方式可以實(shí)現(xiàn)downloadAllAsync不依賴不可預(yù)期的事件執(zhí)行順序而總能提供預(yù)期結(jié)果。我們不將每個(gè)結(jié)果放置到數(shù)組末尾,而是存儲(chǔ)在其原始的索引位置中。

function downloadAsync(urls, onsuccess, onerror) {
    var length = urls.length,
        result = [];
    
    if (length === 0) {
        setTimeout(onsuccess.bind(null, result), 0);
        return;
    }

    urls.forEach(function(url, i) {
        downloadAsync(url, function(text) {
            if (result) {
                result[i] = text; // store at fixed index

                // race condition
                if (result.length === urls.length) {
                    onsuccess(result);
                }
            }
        }, function(error) {
            if (result) {
                result = null;
                onerror(error);
            }
        });
    });
}

上述代碼依然是不正確的。

假如我們有如下的一個(gè)請(qǐng)求。

downloadAllAsync(["huge.txt", "medium.txt", "tiny.txt"]);

根據(jù)數(shù)組更新的契約,即設(shè)置一個(gè)索引屬性,總是確保數(shù)組的length屬性值大于索引。

如果tiny.txt文件最先被下載,結(jié)果數(shù)組將獲取索引未2的屬性,這將導(dǎo)致result.length被更新為3。用戶的success回調(diào)函數(shù)被過(guò)早地調(diào)用,其參數(shù)為一個(gè)不完整的結(jié)果數(shù)組。

正確地實(shí)現(xiàn)應(yīng)該是使用一個(gè)計(jì)數(shù)器來(lái)追蹤正在進(jìn)行的操作數(shù)量。

function downloadAsync(urls, onsuccess, onerror) {
    var pending = urls.length,
        result = [];
    
    if (pending === 0) {
        setTimeout(onsuccess.bind(null, result), 0);
        return;
    }

    urls.forEach(function(url, i) {
        downloadAsync(url, function(text) {
            if (result) {
                result[i] = text; // store at fixed index
                pending--; // register the success

                // race condition
                if (pedding === 0) {
                    onsuccess(result);
                }
            }
        }, function(error) {
            if (result) {
                result = null;
                onerror(error);
            }
        });
    });
}

提示:

JavaScript應(yīng)用程序中的事件發(fā)生是不確定的,即順序是不可預(yù)測(cè)的。

使用計(jì)數(shù)器避免并行操作中的數(shù)據(jù)競(jìng)爭(zhēng)。

絕不要同步地調(diào)用異步的回調(diào)函數(shù)

設(shè)想有downloadAsync函數(shù)的一個(gè)變種,它持有一個(gè)緩存來(lái)避免多次下載同一個(gè)文件。

var cache = new Dict();

function downloadCachingAsync(url, onsuccess, onerror) {
    if (cache.has(url)) {
        onsuccess(cache.get(url)); // synchronous call
        return;
    }

    return downloadAsync(url, function(file) {
        cache.set(url, file);
        onsuccess(file);
    }, onerror);
};

通常情況下,如果可以,它似乎會(huì)立即提供數(shù)據(jù),但這以微妙的方式違反了異步API客戶端的期望。

首先,它改變了操作的預(yù)期順序。

downloadCachingAsync("file.txt", function(file) {
    console.log("finished"); // might happen first
});

console.log("starting");

其次,異步API的目的是維持事件循環(huán)中每輪的嚴(yán)格分離。這簡(jiǎn)化了并發(fā),通過(guò)減輕每輪事件循環(huán)的代碼量而不必?fù)?dān)心其他代碼并發(fā)地修改共享的數(shù)據(jù)結(jié)構(gòu)。同步地調(diào)用異步的回調(diào)函數(shù)違反了這一分離,導(dǎo)致在當(dāng)前輪完成之前,代碼用于執(zhí)行一輪隔離的事件循環(huán)。

downloadCachingAsync(remaining[0], function(file) {
    remaing.shift();

    // ...
});

status.display("Downloading " + remaining[0] + "...");

如果同步地調(diào)用該回調(diào)函數(shù),那么將顯示錯(cuò)誤的文件名的消息(或者更糟糕的是,如果隊(duì)列為空會(huì)顯示undefined)。

同步地調(diào)用異步的回調(diào)函數(shù)甚至可能會(huì)導(dǎo)致一些微妙的問(wèn)題。

異步的回調(diào)函數(shù)本質(zhì)上是以空的調(diào)用棧來(lái)調(diào)用,因此將異步的循環(huán)實(shí)現(xiàn)為遞歸函數(shù)是安全的,完全沒(méi)有累計(jì)超越調(diào)用??臻g的危險(xiǎn)。同步的調(diào)用不能保障這一點(diǎn),因而使得一個(gè)表面上的異步循環(huán)很可能會(huì)耗盡調(diào)用棧空間。

另一個(gè)問(wèn)題是異常。對(duì)于上面的downloadCachingAsync實(shí)現(xiàn),如果回調(diào)函數(shù)拋出一個(gè)異常,它將會(huì)在每輪的事件循環(huán)中,也就是開(kāi)始下載時(shí)而不是期望的一個(gè)分離的回合拋出該異常。

為了確??偸钱惒降卣{(diào)用回調(diào)函數(shù),我們可以使用通用的庫(kù)函數(shù)setTimeout在每隔一個(gè)最小的時(shí)間的超時(shí)時(shí)間后給事件隊(duì)列增加一個(gè)回調(diào)函數(shù)。

var cache = new Dict();

function downloadCachingAsync(url, onsuccess, onerror) {
    if (cache.has(url)) {
        var cached = cache.get(url);
        setTimeout(onsuccess.bind(null, cached), 0);
        return;
    }

    return downloadAsync(url, function(file) {
        cache.set(url, file);
        onsuccess(file);
    }, onerror);
};

提示:

即使可以立即得到數(shù)據(jù),也絕不要同步地調(diào)用異步回調(diào)函數(shù)。

同步地調(diào)用異步的回調(diào)函數(shù)擾亂了預(yù)期的操作序列,并可能導(dǎo)致意想不到的交錯(cuò)代碼。

同步地調(diào)用異步的回調(diào)函數(shù)可能導(dǎo)致棧溢出或錯(cuò)誤地處理異常。

使用異步的API,比如setTimeout函數(shù)來(lái)調(diào)度異步回調(diào)函數(shù),使其運(yùn)行于另一個(gè)回合。

使用promise模式清潔異步邏輯

構(gòu)建異步API的一種流行的替代方式是使用promise(有時(shí)也被稱為deferred或future)模式。

基于promise的API不接收回調(diào)函數(shù)作為參數(shù),相反,它返回一個(gè)promise對(duì)象,該對(duì)象通過(guò)其自身的then方法接收回調(diào)函數(shù)。

var p = downloadP("file.txt");

p.then(function(file) {
    console.log("file: " + file);
});

promise的力量在于它們的組合性。傳遞給then的回調(diào)函數(shù)不僅產(chǎn)生影響,也可以產(chǎn)生結(jié)果。通過(guò)回調(diào)函數(shù)返回一個(gè)值,可以構(gòu)造一個(gè)新的promise。

var fileP = downloadP("file.txt");

var lengthP = fileP.then(function(file) {
    return file.length;
});

lengthP.then(function(length) {
    console.log("length: " + length);
});

promise可以非常容易地構(gòu)造一個(gè)實(shí)用程序來(lái)拼接多個(gè)promise的結(jié)果。

var filesP = join(downloadP("file1.txt"), downloadP("file2.txt"), downloadP("file3.txt"));

filesP.then(function(files) {
    console.log("file1: " + files[0]);
    console.log("file2: " + files[1]);
    console.log("file3: " + files[2]);
});

promise庫(kù)也經(jīng)常提供一個(gè)叫做when的工具函數(shù)。

var fileP1 = downloadP("file1.txt"), 
    fileP2 = downloadP("file2.txt"), 
    fileP3 = downloadP("file3.txt");

when([fileP1, fileP2, fileP3], function(files) {
    console.log("file1: " + files[0]);
    console.log("file2: " + files[1]);
    console.log("file3: " + files[2]);
});

使promise成為卓越的抽象層級(jí)的部分原因是通過(guò)then方法的返回值來(lái)聯(lián)系結(jié)果,或者通過(guò)工具函數(shù)如join來(lái)構(gòu)成promise,而不是在并行的回調(diào)函數(shù)間共享數(shù)據(jù)結(jié)構(gòu)。這本質(zhì)上是安全的,因?yàn)樗苊饬藬?shù)據(jù)競(jìng)爭(zhēng)。

有時(shí)故意創(chuàng)建某種類的數(shù)據(jù)競(jìng)爭(zhēng)是有用的。promise為此提供了一個(gè)很好的機(jī)制。例如,一個(gè)應(yīng)用程序可能需要嘗試從多個(gè)不同的服務(wù)器上同時(shí)下載同一份文件,而選擇最先完成的那個(gè)文件。

var fileP = select(downloadP("http://example1.com/file.txt"), 
                    downloadP("http://example1.com/file.txt"),
                    downloadP("http://example1.com/file.txt"));

fileP.then(function(file) {
    console.log("file: " + file);
});

select函數(shù)的另一個(gè)用途是提供超時(shí)來(lái)終止長(zhǎng)時(shí)間的操作。

var fileP = select(downloadP("file.txt"), timeoutErrorP(2000));

fileP.then(function(file) {
    console.log("file: " + file);
}, function(error) {
    console.log("I/O error or timeout: " + error);
});

提示:

promise代表最終值,即并行完成時(shí)最終產(chǎn)生的結(jié)果。

使用promise組合不同的并行操作。

使用promise模式的API避免數(shù)據(jù)競(jìng)爭(zhēng)。

在要求有意的競(jìng)爭(zhēng)條件時(shí)使用select(也被稱為choose)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/94958.html

相關(guān)文章

  • 前端每周清單第 29 期:Web 現(xiàn)狀分析與優(yōu)化策略、Vue 單元測(cè)試、Headless Chrom

    摘要:前端每周清單第期現(xiàn)狀分析與優(yōu)化策略單元測(cè)試爬蟲(chóng)作者王下邀月熊編輯徐川前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開(kāi)發(fā)教程工程實(shí)踐深度閱讀開(kāi)源項(xiàng)目巔峰人生等欄目。 showImg(https://segmentfault.com/img/remote/1460000011008022); 前端每周清單第 29 期:Web 現(xiàn)狀分析與優(yōu)化策略...

    HackerShell 評(píng)論0 收藏0
  • 前端每周清單半年盤(pán)點(diǎn) Node.js 篇

    摘要:前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開(kāi)發(fā)教程工程實(shí)踐深度閱讀開(kāi)源項(xiàng)目巔峰人生等欄目。對(duì)該漏洞的綜合評(píng)級(jí)為高危。目前,相關(guān)利用方式已經(jīng)在互聯(lián)網(wǎng)上公開(kāi),近期出現(xiàn)攻擊嘗試爆發(fā)的可能。 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn);分為新聞熱點(diǎn)、開(kāi)發(fā)教程、工程實(shí)踐、深度閱讀、開(kāi)源項(xiàng)目、巔峰人生等欄目。歡...

    kid143 評(píng)論0 收藏0
  • 編寫(xiě)質(zhì)量JavaScript代碼數(shù)組和字典

    摘要:但實(shí)例化該構(gòu)造函數(shù)仍然得到的是的實(shí)例?;蛘撸瑸榱吮苊庠谒胁檎覍傩缘牡胤蕉疾迦脒@段樣本代碼,我們可以將該模式抽象到的構(gòu)造函數(shù)中。該構(gòu)造函數(shù)封裝了所有在單一數(shù)據(jù)類型定義中編寫(xiě)健壯字典的技術(shù)細(xì)節(jié)。 參考書(shū)籍:《Effective JavaScript》 數(shù)組和字典 對(duì)象是JavaScript中最萬(wàn)能的數(shù)據(jù)結(jié)構(gòu)。取決于不同的環(huán)境,對(duì)象可以表示一個(gè)靈活的鍵值關(guān)聯(lián)記錄,一個(gè)繼承了方法的面向?qū)ο髷?shù)...

    izhuhaodev 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<