jsは非同期の操作のいくつかの方法を処理します.


概論
JavaScriptは単一スレッドで実行される言語なので、時間がかかるタスクを処理する際には、非同期プログラムが重要になります.jsは非同期処理の最も伝統的な方法はコールバック関数で、基本的にすべての非同期動作はコールバック関数で処理できます.コードをより優雅にするために、イベントモニター、リリース/購読モード、Promiseなどで非同期の操作を処理したいと考えられています.その後、ES 2015言語標準にPromiseを導入しました.これからはブラウザのプロミスをサポートします.また、ES 2015の生成器generatorは、割り込み/復帰実行および転送値などの優れた機能のために非同期処理にも利用されている.その後、ES 2017言語規格は、より優れた非同期処理方法async/awaitを導入した.
非同期処理方式
これらの非同期処理方式の利点と不足をより直感的に発見するために、それぞれ異なる方法を使って同じ非同期問題を解決します.問題:もし原生XMLttpRequestで2つのjsonデータを取得する必要があると仮定します.まず広州の天気を非同期に取得し、成功したら番禺の天気を獲得し、最後に一緒に獲得した2つのjsonデータを出力します.前提:PromisegeneratorasyncPromise.
コールバック関数
最初に最も伝統的なコールバック関数で処理します.
var xhr1 = new XMLHttpRequest();
xhr1.open('GET', 'https://www.apiopen.top/weatherApi?city=  ');
xhr1.send();
xhr1.onreadystatechange = function() {
    if(this.readyState !== 4)  return;
    if(this.status === 200) {
        data1 = JSON.parse(this.response);
        var xhr2 = new XMLHttpRequest();
        xhr2.open('GET', 'https://www.apiopen.top/weatherApi?city=  ');
        xhr2.send();
        xhr2.onreadystatechange = function() {
            if(this.readyState !== 4)  return;
            if(this.status === 200) {
                data2 = JSON.parse(this.response);
                console.log(data1, data2);
            }
        }
    }
};
長所:簡単、便利、実用.短所:逆転関数が形成されやすい地獄.非同期動作が一つしかないなら、コールバック関数で処理するのは全く問題がないです.もし私たちがコールバック関数にもう一つのコールバック関数を組み込むと問題は大きくないです.しかし、多くのコールバック関数を入れようとすると、問題が大きくなります.複数の非同期操作により、強い結合が形成され、コードが乱雑になり、管理できなくなります.この場合は「コールバック関数地獄」と呼ばれます.
事件の傍受
イベントの傍受方法を使う:
var events = new Events();
events.addEvent('done', function(data1) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://www.apiopen.top/weatherApi?city=  ');
    xhr.send();
    xhr.onreadystatechange = function() {
        if(this.readyState !== 4)  return;
        if(this.status === 200) {
            data1 = JSON.parse(data1);
            var data2 = JSON.parse(this.response);
            console.log(data1, data2);
        }
    }
});

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.apiopen.top/weatherApi?city=  ');
xhr.send();
xhr.onreadystatechange = function() {
    if(this.readyState !== 4)  return;
    if(this.status === 200) {
        events.fireEvent('done', this.response);
    }
};
上記のコードはイベントモニターEventsを実現する必要があります.利点:コールバック関数に比べて、イベント傍受方式はコードの結合を実現し、二つのコールバック関数を分離し、コードの管理を容易にする.短所:使うのが不便で、毎回手動でイベントをバインドしてトリガします.発表/購読モードはこれと似ています.多くは言いません.
Promise
ES 6 Promiseを使う方式:
new Promise(function(resolve, reject) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://www.apiopen.top/weatherApi?city=  ');
    xhr.send();
    xhr.onreadystatechange = function() {
        if(this.readyState !== 4)  return;
        if(this.status === 200) return resolve(this.response);
        reject(this.statusText);
    };
}).then(function(value) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://www.apiopen.top/weatherApi?city=  ');
    xhr.send();
    xhr.onreadystatechange = function() {
        if(this.readyState !== 4)  return;
        if(this.status === 200) {
            const data1 = JSON.parse(value);
            const data2 = JSON.parse(this.response);
            console.log(data1, data2);
        }
    };
});
利点:Promiseを使用して、我々は、前の2つの方法よりも論理が強く、実行順序がより明確になるように、コールバック関数ネストをチェーン呼び出しに成功した.短所:コード冗長性、非同期動作は全部thenコンストラクタおよびyield方法に包まれています.本体コードは明らかではなく、意味がはっきりしなくなります.
generator+コールバック関数
次に,generatorとコールバック関数を用いて実現した.まず一つのgenerator functionで非同期動作の論理コードをカプセル化する.
function* gen() {
    const data1 = yield getJSON_TH('https://www.apiopen.top/weatherApi?city=  ');
    const data2 = yield getJSON_TH('https://www.apiopen.top/weatherApi?city=  ');
    console.log(data1, data2);
}
このコードを見たら、直感的で優雅な感じがしますか?実際には、星番号とgeneratorキーワードを除いて、このコードは同期コードと同じになります.もちろん、このgen関数だけは無駄であり、直接実行すればgetJSON_THオブジェクトしか得られない.gen関数の実行を再開/一時停止するためには、その戻ったgeneratorオブジェクトを用いて、データをgen関数に伝達する必要がある.非同期動作の本体コードをgetJSON_THの関数でカプセル化する.
function getJSON_TH(url) {
    return function(fn) {
        const xhr = new XMLHttpRequest();
        
        xhr.open('GET', url);
        xhr.responseType = "json";
        xhr.setRequestHeader("Accept", "application/json");
        xhr.send();
        
        xhr.onreadystatechange = function() {
            if(this.readyState !== 4)  return;
            let err, data;
            if(this.status === 200) {
                data = this.response;
            } else {
                err = new Error(this.statusText);
            }
            fn(err, data);
        }
    }
}
ある学生はgetJSON_TH関数に直接urlとfnの二つのパラメータを入れればいいと思っていますが、なぜ関数を返さなければならないですか?これはまさに奥妙であり、Thunk関数が返した関数はThunk関数であり、パラメータとしては1つのコールバック関数しか受信していない.Thunk関数またはyield関数のコールバック関数によって、gen関数の実行を回復するとともに、gen関数の外部から内部にデータを入力することができる.node.jsでは、Thunkifyモジュールを通じて、バンドコールパラメータの関数をThunk関数に変換できます.次に、私達は手動でgen関数を実行します.
const g = gen();

g.next().value((err, data) => {
    if(err) return g.throw(err);
    g.next(data).value((err, data) => {
        if(err) return g.throw(err);
        g.next(data);
    })
});
ここで、g.next().valueはgen関数のThunkが出力した値であり、これまで述べたThunk関数であり、私たちはそのコールバック関数の中で、g.next(data)法により、dataをgen関数のdata 1に伝達し、gen関数の実行を再開する(gen関数の実行文脈を再度コールスタックに押し込む).便宜上、我々は自動的にgen関数を実行する操作をカプセル化することもできます.
function run(gen) {
    const g =  gen();
    
    function next(err, data) {
        if(err) return g.throw(err);
        const res = g.next(data);
        if(res.done) return;
        res.value(next);
    }
    
    next();
}

run(gen);
利点:generator方式は非同期操作に近い、非常に簡潔明瞭である.また、genがyield文を実行する時は、コンテキストを実行するだけで一時的にポップアップし、破壊することはなく、コンテキストを保存します.短所:プロセス管理が不便で、generator関数を実行するためのアクチュエータが必要です.
generator+PromisePromise関数以外にも、getJSON_PMオブジェクトを介して、generator関数を実行することができます.同じ優雅な論理コード:
function* gen() {
    const data1 = yield getJSON_PM('https://www.apiopen.top/weatherApi?city=  ');
    const data2 = yield getJSON_PM('https://www.apiopen.top/weatherApi?city=  ');
    console.log(data1, data2);
}
co関数は、Promiseオブジェクトを返します.
function getJSON_PM(url) {
    return new Promise((resolve, rejext) => {
        const xhr = new XMLHttpRequest();
        
        xhr.open('GET', url);
        xhr.responseType = "json";
        xhr.setRequestHeader("Accept", "application/json");
        xhr.send();
        
        xhr.onreadystatechange = function() {
            if(this.readyState !== 4) return;
            if(this.status === 200) return resolve(this.response);
            reject(new Error(this.statusText));
        };
    });
}
generator関数を手動で実行します.
const g = gen();

g.next().value.then(data => {
    g.next(data).value.then(data => g.next(data), err => g.throw(err));
}, err => g.throw(err));
自動的にgenerator関数を実行します.
function run(gen) {
    const g = gen();
    
    function next(data) {
        const res = g.next(data);
        if(res.done) return;
        res.value.then(next);
    }
    
    next();
}

run(gen);
generator+coモジュール
node.jsのgeneratorモジュールは、co(gen)関数を自動的に実行するためのモジュールであり、その入口はPromise関数であり、これは、generatorオブジェクトまたはgenerator関数を受信することを期待してパラメータとして、yieldオブジェクトに戻る.
パラメータgen関数では、co文は、generatorオブジェクト、generator関数、thunk関数、Promiseオブジェクト、配列またはオブジェクトを受信すると予想される.Promiseモジュールの主な実現原理は、yield受信値を一つのyieldオブジェクトに統一して変換した後、前述のgenerator+Promiseのような方法で自動的にgenerator関数を実行することである.
以下は私がnode.js coモジュールのソースコードによって修正したes 6 coモジュールです.それをもっと自分に合うようにします.thunkは、yield関数を受信する.
import co from './co.mjs'

function* gen() {
    const data1 = yield getJSON_TH('https://www.apiopen.top/weatherApi?city=  ');
    const data2 = yield getJSON_TH('https://www.apiopen.top/weatherApi?city=  ');
    console.log(data1, data2);
}

co(gen);
Promiseは、asyncオブジェクトを受信する.
function* gen() {
    const data1 = yield getJSON_PM('https://www.apiopen.top/weatherApi?city=  ');
    const data2 = yield getJSON_PM('https://www.apiopen.top/weatherApi?city=  ');
    console.log(data1, data2);
}

co(gen);
async/awaitgenerator関数は、coモジュールなどの実行器を備えたgenerator関数のシンタックス飴である.async関数のawaitキーワードは、Promiseオブジェクトを受信することが期待され、Promiseオブジェクトでない場合は元の値に戻り、その適用性はcoアクチュエータよりも広くなる.async関数は、Promiseオブジェクトに戻り、これは、async関数がgeneratorオブジェクトに戻るgenerator関数よりも実用的であるように、coアクチュエータと同じである.async関数が順調に実行されれば、戻ってきたPromiseオブジェクトの状態はful filledになり、value値はasync関数のreturnキーワードの戻り値となります.async関数が実行中にエラーが発生し、async内部でエラーが発生していない場合、戻ってきたPromiseオブジェクトの状態はrejectになり、ason値はasync関数のエラーになります.awaitは、Promiseオブジェクトのみを処理する.
async function azc() {
    const data1 = await getJSON_PM('https://www.apiopen.top/weatherApi?city=  ');
    const data2 = await getJSON_PM('https://www.apiopen.top/weatherApi?city=  ');
    console.log(data1, data2);
}

azc();
async関数は、generator関数の自動実行器を、言語レベルで提供し、ユーザに露出しないように変更する.
async function fn(args) {
  // ...
}
相当于:
function fn(args) {
  return exec(function* () {
    // ...
  });
}
利点:最も簡潔で、最も語義に合っていて、同期コードに一番近いです.複数のPromise非同期操作に最適です.generator方式に比べて、async方式は自動アクチュエータを省き、コード量を減らしました.短所:js言語のASyncアクチュエータの機能性は、coモジュールなどのアクチュエータが強くないかもしれません.あなたは自分の需要に応じて自分のgenerator関数アクチュエータを定義できます.
参照リンク:https://github.com/lyl123321/...