這篇文章需要對javascript的promise有基本的認識,對不熟的讀者可能不太友善,需要自行google,請大家海涵orz

這一次的教學我們接續上一篇 python的asyncio模組(五):Future對象與Task對象 的內容。

在上一篇結尾我們用了Future對象去模擬javascript的promise物件的行為,但還沒有解釋程式的實際內容,所以就留到這次的教學做解說。

接續上一篇內容

程式我就不貼了,我覺得放在分頁或是放在另一個視窗一起看比較方便。

先把重點放在最末尾的幾行:

loop = asyncio.get_event_loop()
success_future = loop.create_future()
fail_future = loop.create_future()

不像javascript本身內置Event loop,python必須明確的定義出一個Event loop來實行異步程式。

然後我們用loop.create_future創了兩個Future物件,success_future和fail_future,另外要注意一件事,因為Future本身就是用來執行異步程式的,所以在初始化時,就會綁定一個Event loop,也就是Future裏面會有一個內置成員self._loop,也就是我們剛剛創立的loop變數。

success_future.add_done_callback(success_callback)
fail_future.add_done_callback(fail_callback)

我們讓兩個Future物件各自增加一個callback,當我們執行future.set_result()和future.set_exception()的時候(也就是上一篇說的當我們指定了任務的結果),這些callback就會自動執行。

之所以能夠自動執行callback就是因為Future在set_result()或是set_exception()之後會驅動內置成員self._loop去執行先前加入的callback。

loop.call_soon(promise_example, 'success', success_future)
loop.call_soon(promise_example, 'fail', fail_future)
loop.run_until_complete(asyncio.wait([success_future, fail_future]))

之前的文章python的asyncio模組(四):Event loop常用API似乎沒有提到call_soon的用法,那是因為call_soon是比較底層的用法,在asyncio的一般應用情境下,是不會使用到call_soon,也不會真的操作Future對象,但以教學的目的來說,稍微了解這些底層的運作也是重要的。

call_soon(func, *args)會指定一個要執行的function和要放進func的參數,當啟動Event loop時就會直接執行被放入call_soon的func。

最後一行的用法在python的asyncio模組(三):建立Event Loop和定義協程都有提到:

  • run_until_complete指定某個任務完成就會停止Event loop,可以傳入coroutine或Task或Future。
  • asyncio.wait把一個任務(可以是coroutine或Task或Future)清單組成一個大的任務。
    所以最後一行指的是如果success_future和fail_future都已經被指定任務的結果且執行完各自的callback就會停止Event loop。
  • 接下來我們來把上篇文章的javascript程式和python程式做一個功能上的比對,這樣我們就更能了解我們的Future是如何模擬出promise的功能。

    1. Future物件相當於js的promise物件

    js程式裡的success_promise和fail_promise相當於python程式裡的success_future和fail_future,雖然結構上有點不一樣,比如說js的promise一開始就直接包住一個function,而Future只是作為一個參數互相傳遞。

    但是他們扮演的角色都是差不多的,他們得到了一個結果後就會呼叫對應的callback,像下面第四點和第五點所說的。

    2. future._loop.call_later相當於js裡的setTimeout

    js程式裡的setTimeout會提醒其內置的Event loop在1000毫秒之後執行setTimeout裏面的callback,而python程式的future則會呼叫其內部變數_loop的一個方法call_later說,在1秒後呼叫setTimeout_func,這是一樣的概念。

    call_later也跟call_soon一樣,是比較底層的用法,顧名思義,call_later就是在經過一定時間後才會呼叫某個function。

    3. setTimeout_func相當於js中setTimeout裡面放進去的callback

    4. future.set_result()相當於js promise裡面的resolve()

    5. future.set_exception()相當於js promise裡面的reject()

    js程式中setTimeout裡面的callback根據success_or_fail的值來決定呼叫resolve('success')或是reject(new Error('fail')),這就跟python程式中的setTimeout_func,根據success_or_fail的值來決定呼叫future.set_result('success')或是future.set_exception(Exception('fail'))。

    6. success_callback融合了js程式裡的success_promise.then和success_promise.catch

    7. fail_callback融合了js程式裡的fail_promise.then和fail_promise.catch

    歸功於剛剛解說的:

    success_future.add_done_callback(success_callback)
    fail_future.add_done_callback(fail_callback)
    

    success_future只要被指定結果(set_result或是set_exception)就會呼叫success_callback。
    fail_future只要被指定結果(set_result或是set_exception)就會呼叫fail_callback。

    然而success_callback和fail_callback全都同時包辦js程式中的then和catch,之所以能同時融合兩種功能是因為success_callback和fail_callback裏面都做了try/catch的處理。

    不知道這次文章會寫這麼長,還沒講到Task感覺就已經到了一個篇幅的量。

    會囉哩叭唆這麼多,主要還是想好好解釋Future如何在底層實現異步執行,希望大家能看懂。

    下一次的教學會講解使用Coroutin和Task對象後如何改善異步程式的可讀性,然後我們會發現一件很重要的事情,Javascript為了改善異步程式的可讀性,從ES6的Promise推進到ES7的async/await,這與Future對象的使用進展到Task對象的使用是類似的概念。

    下一篇教學:
    python的asyncio模組(七):Future對象與Task對象(三)