javascriptのPromiseをpythonで実装する方法


Motive

selenium でスマホ版でブラウザを起動しようと思った時、

    sp_args = "--user-agent="
    sp_args_list = ["Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X)",
                    "AppleWebKit/602.3.12 (KHTML, like Gecko)",
                    "Version/10.0 Mobile/14C92 Safari/602.1"]
    sp_args += " ".join(sp_args_list)

と設定するだけでは一部のサイトではレスポンシブになっているため対応しない場合があります。

そのため、 width を450くらいにしてリロードをするとスマホ版の表示になるのですが、ブラウザ設定 → スマホ画面の大きさを設定 -> リロード の順に一つのプロセスが完了したことがcompleteになった状態で次のプロセスに移す実装ができないかと思ったわけです。

で、 javascriptの非同期処理で使われるPromiseと同等のものがpythonでできないかと考えたわけっす。

Preparetion

pip で探すと早速パッケージが見つかりました。ふつーに

pip install Promise

で良いです。

Model

参考にしたサンプルコードですが初めてのJavaScript 第3版 ――ES2015以降の最新ウェブ開発 14章 非同期プログラミングにあったロケット発射モデルを題材にしました。

元コードからブラウザに表示する編集等々でちょっと変えています。


<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script type="text/javascript" src="./sample.js"></script>
    </head>
    <body>
        <h3>apolonX</h3>
        <div class="box">
        </div>
    </body>

</html>

$(()=>{

    countdown(10)
    .then(()=>{
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                //大気圏突入
                resolve(insertBox("atmosphere..."));
            }, 2000);
        });
    })
    .then(()=>{
        //成功
        insertBox("success!!");
    });

});

//カウントダウン
function countdown(seconds){
    let dst = new Promise(
            (resolve, reject) => {
                for (let i=seconds;0 <=i;i--){
                    setTimeout(() => {
                        if(i>0){
                            insertBox(countSign(i));
                        } else {
                            resolve(insertBox(goSign()));
                        }
                    }, (seconds-i)*1000);
                }
            }
        );
    return dst
}


function insertBox(src){
    let p = $("<p>")
    p.text(src);
    $(".box").append(p);
}

function countSign(index){
    return index + "...";
}

function goSign(){
    return "GO!";
}

で約2秒ごとにブラウザにメッセージが表示されます。

Method

from promise import Promise
import time

def main():
    countdown(10).then(go()).then(Promise.resolve(atmosphere())).then(success())

def countdown(seconds):
    def myfunc(resolve, reject):
        for i in reversed(range(0,seconds + 1)):
            if 0 < i:
                print( str(i) + "..." )
                time.sleep(2)
            else :
                resolve()
    return Promise(myfunc)

def go():
    time.sleep(1)
    print("GO")


def atmosphere():
    time.sleep(1)
    print("atmosphere")

def success():
    print("success!!")


if __name__ == '__main__':
    main()

countdown(10).then(go()).then(Promise.resolve(atmosphere())).then(success())
でチェイン化しています。

Future

複数のAPIでデータ取得後に一括処理して文章生成するなどのケースであったら、全てのAPIのデータ取得後に一括処理する一貫性が保たれるので何かと良い。
簡易的なトランザクション処理ですかね。

Reference

promise
promise:github
初めてのJavaScript 第3版 ――ES2015以降の最新ウェブ開発 14章 非同期プログラミング
Python の Promise 実装とその活用方法について / How to implement Promise by Python and how to use it