Javascript 從異步函數到 Promise 到 Async/Await

我最近正在看的一本書《聊聊架構》,在進入今天的主題以前,我想和你們分享這本書裏的一個概念「生命週期」。javascript

大體是這麼說的:html

人類的生命很短,百年也只有短短的三萬六千天。大部分人都不肯意接受一切都將消逝的事實,總想活得更久,佔有更多,享受更多。在人們短短的一輩子中,如何延長自身的生命呢?一個辦法就是儘量作出更多的成就,可以讓更多的人生活得更好。在一樣的時間內創造出更多的產出,至關於把本身的生命延長了。前端

其中有效的作法就是將每次活動進行拆分,本身執行核心的【生命週期】,將【非核心生命週期】交給其它主體進行。java

好比,用戶購物這一場景,從用戶進入到商店,進行瀏覽、詢問、購買等活動,到離開商店,都是按時間順序一步一步在執行的。對於咱們來講,這其中的每一步都須要時間,對於現代人來講,太奢侈了。若是這時候,咱們把這個購物行爲進行拆分,把選購交由別人執行,由別人代替用戶上街選擇和過濾,或者經過網上推薦等來完成選購,用戶只須要最後肯定物品的挑選便可。用戶的目的是買到本身須要的東西,而不是選購自己。這樣就能夠大大節省用戶的時間,將更多的精力放到其餘核心事情上。數據庫

再好比,如今吃飯,須要本身到菜市場買菜、回來煮飯煮菜,吃完飯後,還須要本身洗碗等。有時候徹底能夠叫外賣的,肚子餓了,點點【餓了嗎】,接着就能夠繼續作本身的事情了,坐等外賣送到。將【煮飯】這一耗時的事情交給別人來作。編程

在編程語言中,這就是同步與異步的區別。異步的做用就是將耗時的事情交給【別人】來作,本身繼續進行;當【別人】作完事情後,執行回調函數,帶回結果,交回本身執行。數組

回調函數

雖然 Javascipt 語言是「單線程」執行環境,但在執行模式下,分紅同步和異步兩種模式,其中咱們更多的使用回調函數的方式來進行異步操做,如:promise

blogs.search = (words, res) => {
    const titleQuery = new AV.Query(Blog)
    titleQuery.contains('title', words);

    const descQuery = new AV.Query(Blog)
    descQuery.contains('desc', words);

    const tagsQuery = new AV.Query(Blog)
    tagsQuery.contains('tags', words);

    const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery);

    wordsQuery.descending('createdAt');
    wordsQuery.limit(5);
    wordsQuery.find().then(function (results) {
        res(results);
    }, function (error) {
        res([]);
    });
}

這函數的做用就是經過關鍵詞words搜索,獲取知足條件的公衆號文章,若是找不到就返回空數組。其中這裏的res就是一個回調函數。在使用處:架構

server.route({
        method: 'POST',
        path: '/searchblog',
        handler: function (request, reply) {
            const words = request.payload.words;
            ModelBlog.search(words, results => {
                let data = [];
                results.forEach(function(v) {
                    let wrap = {};

                    wrap.title = v.get('title');
                    wrap.description = v.get('desc');
                    wrap.picurl = v.get('picurl');
                    wrap.url = v.get('url');

                    data.push(wrap);
                });
                reply(data);
            });
        },
        config: {
            validate: {
                payload: {
                    words: Joi.string().required()
                }
            }
        }
    });

一樣的,在handler函數中的reply也是一個回調函數,先經過ModelBlog.search函數內嵌回調函數獲取數據庫中的文章數組,而後再對回調結果進行處理,返回給回調函數reply,最後返回給前端使用。curl

經過一個簡單的例子——「回調函數,內嵌回調函數」來講明回調函數是能夠無窮盡的內嵌,結果就是各個部分之間高度耦合,流程混在一塊兒,每一個任務只能指定一個回調函數。最終形成的結果就會嵌入回調地獄,極可能就像這樣了——結尾是無止境的});

圖片來自於:
https://tutorialzine.com/media/2017/07/callback-hell.jpg

Promise 來源

所謂 Promise, 就是一個對象,用來傳遞異步操做的消息。它表明了某個將來纔會知道結果的事件 (一般是一個異步操做),而且這個事件提供統一的 API,可供進一步處理。

————來自《ES 6標準入門 (第二版)》

基於回調函數的異步處理若是統一參數使用規則的話,寫法也會很明瞭。可是,這也僅是編碼規範而已,即便採用不一樣的寫法也不會出錯。

而 Promise 則是把相似的異步處理對象和處理規則進行規範化,並按照採用統一的接口來編寫,而採用規定方法以外的寫法都會出錯。

除了 Promise 對象規定的方法 (這裏的 then 或 catch )之外的方法都是不可使用的,而不會像回調函數方式那樣能夠本身自由的定義回調函數的參數,而必須嚴格遵照固定、統一的編碼方式來編寫代碼。

這樣,基於 Promise 的統一接口的作法,就能夠造成基於接口的各類各樣的異步處理模式。因此,Promise 的功能是能夠將複雜的異步處理輕鬆的進行模式化,這也能夠說是使用 Promise 的理由之一。

Promise 在規範上規定 Promise 只能使用異步調用方式。
若是將上面的 demo 從新用 Promise 來寫呢:

blogs.promise_search = (words) => {
    const promise = new Promise(function (resolve, reject) {
        const titleQuery = new AV.Query(Blog)
        titleQuery.contains('title', words);

        const descQuery = new AV.Query(Blog)
        descQuery.contains('desc', words);

        const tagsQuery = new AV.Query(Blog)
        tagsQuery.contains('tags', words);

        const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery);

        wordsQuery.descending('createdAt');
        wordsQuery.limit(5);
        wordsQuery.find().then(function (results) {
            resolve(results);
        }, function (error) {
            reject(error);
        });
    });

    return promise;
}

Promise 構造函數接受一個函數做爲參數,該函數的兩個參數分別是 resolve 和 reject。它們是兩個函數,由 Javascript 引擎提供,不用本身傳入。

其中,resolve 函數的做用是,將 Promise 對象的狀態從「未完成」變成「成功」 (即從 Pending 變爲 Resolved),在異步操做成功時調用,並將異步操做的結果做爲參數傳遞出去;

reject 函數的做用是,將 Promise 對象的狀態從「未完成」變爲「失敗」 (即從 Pending 變爲 Rejected),在異步操做失敗時調用,並將異步操做報出的錯誤做爲參數傳遞出去。

Promise 實例執行之後,能夠用then方法分別指定Resolved狀態和Rejected狀態的回調函數。因此在使用處:

const words = request.payload.words;
ModelBlog.promise_search(words).then(function (results) {
    let data = [];
    results.forEach(function(v) {
        let wrap = {};

        wrap.title = v.get('title');
        wrap.description = v.get('desc');
        wrap.picurl = v.get('picurl');
        wrap.url = v.get('url');

        data.push(wrap);
    });
    reply(data);
}).catch(function (error) {
    reply([]);
});

其中,then函數接受回調函數,做爲 Promise 對象的狀態變爲 Resolved 時調用;而 catch 回調函數做爲 Promise 對象的狀態變爲 Rejected 時調用。和【異步函數】相比,簡單明瞭不少了,至少不用再傳遞迴調函數到 ModelBlog 中,能夠作到代碼的分離,ModelBlog 的做用只是爲了拿到數據,返回 Promise 對象,具體外界怎麼使用,那是別人的事情了;一樣在使用方,能夠直接調用 Promise 對象,經過 then 方法處理回調數據和錯誤信息,代碼也就更容易理解了。

但寫代碼總不能處處都是 Promise 對象,既然 Promise 能解決異步調用地獄的問題,但還有沒有更好的辦法將 Promise 異步方法寫的和同步寫法那樣,畢竟不少人已經習慣面向過程的編寫方式了?

Async/Await

Async/Await是一個好久就使人期待的 JavaScript 功能,它讓使用異步函數更加愉快和容易理解。它是基於 Promise 的而且和現存的全部基於 Promise 的 API 相兼容。

從 async 和 await 這兩個名字來的這兩個關鍵字將會幫助咱們整理咱們的異步代碼。

async function getBlogsAsync(words) {
    const titleQuery = new AV.Query(Blog)
    titleQuery.contains('title', words);

    const descQuery = new AV.Query(Blog)
    descQuery.contains('desc', words);

    const tagsQuery = new AV.Query(Blog)
    tagsQuery.contains('tags', words);

    const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery);

    wordsQuery.descending('createdAt');
    wordsQuery.limit(5);
    let results = await wordsQuery.find();
    return results;
}

這下連new Promise(...)都省了,直接寫核心業務代碼。很明顯 Async/Await 版本的代碼更短而且可讀性更強。除了使用的語法,兩個函數徹底相同——他們都返回 Promise 而且都從數據庫獲得 Blogs 數據返回。在使用時,仍是和以前同樣,直接調用getBlogsAsync方法:

getBlogsAsync(words).then(function (results) {
    let data = [];
    results.forEach(function(v) {
        let wrap = {};

        wrap.title = v.get('title');
        wrap.description = v.get('desc');
        wrap.picurl = v.get('picurl');
        wrap.url = v.get('url');

        data.push(wrap);
    });
    reply(data);
}).catch(function (error) {
    reply([]);
});

總結

隨着 Async/Await ,JavaScript語言在代碼可讀性和易用性上向前邁進了一大步。並且寫異步代碼,就跟常規的寫面向過程的同步代碼同樣,簡單直接明瞭。

最後分享幾個相關資料,值得一看,還有更多深刻的內容須要繼續挖掘:

  1. Javascript異步編程的4種方法。http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

  2. 《ES 6標準入門 (第二版)》,做者:阮一峯

  3. JavaScript Async/Await Explained in 10 Minutes. https://tutorialzine.com/2017/07/javascript-async-await-explained

  4. 八段代碼完全掌握 Promise. http://www.javashuo.com/article/p-mkzxierw-gk.html

  5. JavaScript Promise迷你書(中文版) http://liubin.org/promises-book/#promises-overview

  6. 理解 async/await. http://www.javashuo.com/article/p-uhsbtafj-h.html


據說最美的人和最帥的人,都會給做者打賞,以資鼓勵


coding01 期待您關注

qrcode

相關文章
相關標籤/搜索