web-dev-qa-db-ja.com

JavaScriptで一連の関数を同期的に呼び出す方法

データを取得して処理する必要のあるjavascriptプロジェクトに取り組んでいますが、JavaScriptの非同期性に問題があります。やりたいことは次のようなものです。

//The set of functions that I want to call in order
function getData() {
    //gets the data
}

function parseData() {
    //does some stuff with the data
}

function validate() {
    //validates the data
}

//The function that orchestrates these calls 
function runner() {
    getData();
    parseData();
    validate();
}

ここでは、プログラムがデータを取得する前にデータを検証しようとする状況に遭遇しているため、各関数が完了を待ってから次の呼び出しに進むようにします。ただし、テストのためにこれらの関数から値を返せるようにしたいので、これらの関数にブール値を返して完了を確認させることはできません。次の呼び出しに進む前に、関数が完了するまでjavascriptを待機させるにはどうすればよいですか?

5
Nick P

約束を使用する:

//The set of functions that I want to call in order
function getData(initialData) {
  //gets the data
  return new Promise(function (resolve, reject) {
    resolve('Hello World!')
  })
}

function parseData(dataFromGetDataFunction) {
  //does some stuff with the data
  return new Promise(function (resolve, reject) {
    resolve('Hello World!')
  })
}

function validate(dataFromParseDataFunction) {
  //validates the data
  return new Promise(function (resolve, reject) {
    resolve('Hello World!')
  })
}

//The function that orchestrates these calls 
function runner(initialData) {
    return getData(initialData)
        .then(parseData)
        .then(validate)
}

runner('Hello World!').then(function (dataFromValidateFunction) {
    console.log(dataFromValidateFunction);
})

それらは理解しやすいだけでなく、コードの可読性の観点からも完全に理にかなっています。それらについてもっと読む ここ 。ブラウザ環境を使用している場合は、 this polyfillをお勧めします。

9

引用したコードは同期して実行されます。 JavaScript関数呼び出しは同期的です。

したがって、getDataparseData、および/またはvalidateには非同期操作(ブラウザーでのajaxの使用など)が含まれると想定します。またはNodeJSではreadFile)。その場合、基本的に2つのオプションがあり、どちらにもコールバックが含まれます。

1つ目は、これらの関数に、完了時に呼び出すコールバックを受け入れるようにすることです。たとえば、次のようになります。

function getData(callback) {
    someAsyncOperation(function() {
        // Async is done now, call the callback with the data
        callback(/*...some data...*/);
    });
}

あなたはこのようにそれを使うでしょう:

getData(function(data) {
    // Got the data, do the next thing
});

コールバックの問題は、コールバックがcomposeであり、セマンティクスがかなり脆弱であるということです。したがって、promisesは、より良いセマンティクスを提供するために考案されました。 ES2015(別名「ES6」)またはまともなpromiseライブラリでは、次のようになります。

function getData(callback) {
    return someAsyncOperation();
}

または、someAsyncOperationがpromise対応でない場合、次のようになります。

function getData(callback) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(function() {
            // Async is done now, call the callback with the data
            resolve(/*...some data...*/);
            // Or if it failed, call `reject` instead
        });
    });
}

あまり役に立たないようですが、重要なことの1つはcomposability;です。最終的な関数は次のようになります。

function runner() {
    return getData()
        .then(parseData) // Yes, there really aren't () on parseData...
        .then(validate); // ...or validate
}

使用法:

runner()
    .then(function(result) {
         // It worked, use the result
    })
    .catch(function(error) {
         // It failed
    });

これが例です。 PromiseとES2015の矢印関数をサポートするごく最近のブラウザーでのみ機能します。これは、私が怠惰で矢印関数を使用して記述し、Promiselibが含まれていなかったためです。

"use strict";

function getData() {
    // Return a promise
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // Let's fail a third of the time
            if (Math.random() < 0.33) {
                reject("getData failed");
            } else {
                resolve('{"msg":"This is the message"}');
            }
        }, Math.random() * 100);
    });
}

function parseData(data) {
    // Note that this function is synchronous
    return JSON.parse(data);
}

function validate(data) {
    // Let's assume validation is synchronous too
    // Let's also assume it fails half the time
    if (!data || !data.msg || Math.random() < 0.5) {
        throw new Error("validation failed");
    }
    // It's fine
    return data;
}

function runner() {
    return getData()
        .then(parseData)
        .then(validate);
}

document.getElementById("the-button").addEventListener(
    "click",
    function() {
        runner()
            .then(data => {
                console.log("All good! msg: " + data.msg);
            })
            .catch(error => {
                console.error("Failed: ", error && error.message || error);
            });
    },
  false
);
<input type="button" id="the-button" value="Click to test">
(you can test more than once)
4
T.J. Crowder