データを取得して処理する必要のある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を待機させるにはどうすればよいですか?
約束を使用する:
//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をお勧めします。
引用したコードは同期して実行されます。 JavaScript関数呼び出しは同期的です。
したがって、getData
、parseData
、および/または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)