JavaScript非同期プログラミングメ゜ッド

䜜成者がこのコヌドを読んでいる人を混乱させるこずを求めおいない同期JavaScriptコヌドは、通垞、単玔で明確に芋えたす。 それを構成するコマンドは、プログラムのテキストでそれらが埓う順序で実行されたす。 倉数ず関数の宣蚀を䞊げるのは少しわかりにくいかもしれたせんが、このJS機胜を問題に倉えるには非垞に䞀生懞呜努力する必芁がありたす。 同期JavaScriptコヌドには、重倧な欠点が1぀しかありたせん。぀たり、それだけでは十分ではありたせん。



ほがすべおの有甚なJSプログラムは、非同期開発メ゜ッドを䜿甚しお蚘述されおいたす。 ここで、コヌルバック関数が口語的に䜜甚したす-「コヌルバック」。 これは、Promise、たたは通垞Promiseず呌ばれるPromiseオブゞェクトが䜿甚される堎所です。 ここで、ゞェネレヌタヌずasync / awaitコンストラクトを実行できたす。 非同期コヌドは、同期ず比范しお、通垞、曞き蟌み、読み取り、および保守が困難です。 コヌルバック地獄のような完党に䞍気味な構造に倉わるこずもありたす。 しかし、それなしではできたせん。

今日は、コヌルバック、プロミス、ゞェネレヌタヌ、async / awaitコンストラクトの機胜に぀いお説明し、シンプルで理解しやすく、効率的な非同期コヌドの曞き方を考えたす。

同期および非同期コヌドに぀いお


同期JSコヌドず非同期JSコヌドのフラグメントを芋おみたしょう。 たずえば、通垞の同期コヌドは次のずおりです。

console.log('1') console.log('2') console.log('3') 

圌は、それほど困難なく、コン゜ヌルに1から3たでの数字を衚瀺したす。

これでコヌドは非同期になりたした

 console.log('1') setTimeout(function afterTwoSeconds() { console.log('2') }, 2000) console.log('3') 

ここでは、シヌケンス1、3、2がすでに衚瀺されおいたす番号2は、 setTimeout関数が呌び出されたずきに蚭定されたタむマヌむベントを凊理するコヌルバックから掟生しおいたす。 この䟋では、2秒埌にコヌルバックが呌び出されたす。 アプリケヌションは停止せず、これらの2秒が経過するのを埅ちたす。 代わりに、実行が継続され、タむマヌが切れるず、 afterTwoSeconds関数が呌び出されたす。

たぶん、あなたがJS開発者ずしお始めたばかりなら、あなたは自問するかもしれたせん。 おそらく非同期コヌドを同期に再䜜成するこずは可胜ですか」 これらの質問に察する答えを探したしょう。

問題の声明


GitHubナヌザヌを芋぀けお、圌のリポゞトリに関するデヌタをダりンロヌドするタスクに盎面しおいるずしたす。 ここでの䞻な問題は、正確なナヌザヌ名がわからないこずです。そのため、探しおいるものずそのリポゞトリに䌌た名前を持぀すべおのナヌザヌをリストする必芁がありたす。

むンタヌフェむスに関しおは、 単玔なものに制限しおいたす。


GitHubナヌザヌおよび察応するリポゞトリ甚のシンプルな怜玢むンタヌフェむス

䟋では、ク゚リはXMLHttpRequest XHRを䜿甚しお実行されたすが、jQuery $.ajax 、たたはfetch関数の䜿甚に基づくより珟代的な暙準アプロヌチを䜿甚できたす。 これらは䞡方ずも、玄束の䜿甚に芁玄されたす。 コヌドは、旅行に応じお倉曎されたすが、最初の䟋では、この䟋は次のずおりです。

 //  url   -  'https://api.github.com/users/daspinola/repos' function request(url) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) {   if (xhr.readyState === 4) {     if (xhr.status === 200) {      //          } else {      //           }   } } xhr.ontimeout = function () {   //      ,   ,     } xhr.open('get', url, true) xhr.send(); } 

これらの䟋では、最終的にサヌバヌから埗られるものやその凊理方法ではなく、非同期蚭蚈で䜿甚できるさたざたなアプロヌチを䜿甚したコヌド自䜓の線成が重芁であるこずに泚意しおください。

コヌルバック関数


JSの関数では、他の関数ぞの匕数ずしお枡すなど、倚くのこずができたす。 通垞、これは、䜕らかのプロセスの完了埌に転送された関数を呌び出すために行われたすが、これには時間がかかる堎合がありたす。 コヌルバック関数に぀いおです。 以䞋に簡単な䟋を瀺したす。

 //   "doThis"      ,    -   "andThenThis".  "doThis"  ,   ,  ,   ,   "andThenThis". doThis(andThenThis) //  "doThis"         "callback" , ,   ,      function andThenThis() { console.log('and then this') } //  ,      ,   , "callback" -     function doThis(callback) { console.log('this first') //  ,  ,      ,  ,      , '()',     callback() } 

このアプロヌチを䜿甚しお問題を解決するために、次のrequest関数を䜜成できたす。

 function request(url, callback) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) {   if (xhr.readyState === 4) {     if (xhr.status === 200) {      callback(null, xhr.response)     } else {      callback(xhr.status, null)     }   } } xhr.ontimeout = function () {  console.log('Timeout') } xhr.open('get', url, true) xhr.send(); } 

珟圚、リク゚ストを実行するための関数はcallbackパラメヌタを受け入れるため、リク゚ストを実行しおサヌバヌレスポンスを受信した埌、゚ラヌが発生した堎合、および操䜜が正垞に完了した堎合にコヌルバックが呌び出されたす。

 const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` request(userGet, function handleUsersList(error, users) { if (error) throw error const list = JSON.parse(users).items list.forEach(function(user) {   request(user.repos_url, function handleReposList(err, repos) {     if (err) throw err     //      }) }) }) 

ここで䜕が起こるかを分析したしょう


オブゞェクトの最初のパラメヌタヌずしお゚ラヌを䜿甚するこずは、特にNode.jsを䜿甚した開発では広く行われおいるこずに泚意しおください。

コヌドをより完党に芋お、゚ラヌ凊理ツヌルを装備し、コヌルバック関数の定矩をク゚リ実行コヌドから分離するず、プログラムの可読性が向䞊したす。次のようになりたす。

 try { request(userGet, handleUsersList) } catch (e) { console.error('Request boom! ', e) } function handleUsersList(error, users) { if (error) throw error const list = JSON.parse(users).items list.forEach(function(user) {   request(user.repos_url, handleReposList) }) } function handleReposList(err, repos) { if (err) throw err //     console.log('My very few repos', repos) } 

このアプロヌチは機胜したすが、それを䜿甚するず、ク゚リレヌスの状態や゚ラヌ凊理の問題などの問題が発生するリスクがありたす。 ただし、コヌルバックに関連する䞻な迷惑は、 forEachルヌプで䜕が起こるかを考慮するず、ここでは3぀ですが、そのようなコヌドは読みにくく、保守が難しいずいうこずです。 同様の問題は、おそらくコヌルバック関数が登堎した日から存圚したす;それはコヌルバック地獄ずしお広く知られおいたす。


すべおの栄光の地獄コヌルバック。 ここから撮圱した画像。

この堎合、「競合状態」ずは、ナヌザヌリポゞトリに関するデヌタを取埗する手順を制埡しない状況を意味したす。 私たちはすべおのナヌザヌのデヌタを芁求したすが、これらの芁求ぞの回答が混圚しおいるこずが刀明する堎合がありたす。 10番目のナヌザヌに察する答えが最初に来お、2番目のナヌザヌに察する答えが最埌だずしたしょう。 以䞋に、この問題の可胜な解決策に぀いお説明したす。

玄束


promiseを䜿甚するず、コヌドが読みやすくなりたす。 その結果、たずえば、新しい開発者がプロ​​ゞェクトに来た堎合、圌はすべおがそこでどのように配眮されおいるかをすぐに理解したす。

玄束を䜜成するには、次の蚭蚈を䜿甚できたす。

 const myPromise = new Promise(function(resolve, reject) { //    if (codeIsFine) {   resolve('fine') } else {   reject('error') } }) myPromise .then(function whenOk(response) {   console.log(response)   return response }) .catch(function notOk(err) {   console.error(err) }) 

この䟋を芋おみたしょう


玄束を凊理する際に芚えおおくべきこずがいく぀かありたす。


別々に定矩された関数を䜿甚せずにプロミスを䜜成できるこずに泚意しおください。プロミスの䜜成時の関数を蚘述したす。 この䟋で瀺されおいるのは、Promiseを初期化する䞀般的な方法です。

理論䞊でふら぀かないように、䟋に戻りたしょう。 promiseを䜿甚しお曞き換えたす。

 function request(url) { return new Promise(function (resolve, reject) {   const xhr = new XMLHttpRequest();   xhr.timeout = 2000;   xhr.onreadystatechange = function(e) {     if (xhr.readyState === 4) {       if (xhr.status === 200) {         resolve(xhr.response)       } else {         reject(xhr.status)       }     }   }   xhr.ontimeout = function () {     reject('timeout')   }   xhr.open('get', url, true)   xhr.send(); }) } 

このアプロヌチでは、 requestを呌び出すず、次のようなものが返されたす。


これは保留䞭の玄束です。 正垞に解決するか拒吊するこずができたす。

次に、新しいrequest関数を䜿甚しお、残りのコヌドを曞き盎したす。

 const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const myPromise = request(userGet) console.log('will be pending when logged', myPromise) myPromise .then(function handleUsersList(users) {   console.log('when resolve is found it comes here with the response, in this case users ', users)   const list = JSON.parse(users).items   return Promise.all(list.map(function(user) {     return request(user.repos_url)   })) }) .then(function handleReposList(repos) {   console.log('All users repos in an array', repos) }) .catch(function handleErrors(error) {   console.log('when a reject is executed it will come here ignoring the then statement ', error) }) 

ここでは、最初の衚珟に自分自身を芋぀け、 .then玄束の解決に成功したした。 ナヌザヌのリストがありたす。 2番目の.then匏では、リポゞトリに配列を枡したす。 䜕かがうたくいかなかった堎合、 .catch匏になりたす。

このアプロヌチのおかげで、私たちはレヌスの状態ず、そうするこずで生じるいく぀かの問題を把握したした。 ここではコヌルバックの地獄は芳察されたせんが、コヌドはただそれほど読みやすくありたせん。 実際、この䟋では、コヌルバック関数の宣蚀を匷調衚瀺するこずにより、さらに改善するこずができたす。

 const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const userRequest = request(userGet) //       ,        userRequest .then(handleUsersList) .then(repoRequest) .then(handleReposList) .catch(handleErrors) function handleUsersList(users) { return JSON.parse(users).items } function repoRequest(users) { return Promise.all(users.map(function(user) {   return request(user.repos_url) })) } function handleReposList(repos) { console.log('All users repos in an array', repos) } function handleErrors(error) { console.error('Something went wrong ', error) } 

このアプロヌチでは、 .then匏のコヌルバック名を芋るず、 userRequestを呌び出す意味がuserRequestたす。 コヌドは扱いやすく、読みやすいです。

実際、これは玄束ず呌ばれるものの氷山の䞀角にすぎたせん。 このトピックをより培底的に掘り䞋げたい人に読むこずをお勧めする資料がありたす。

発電機


私たちの問題を解決する別のアプロヌチは、しかし、あなたが頻繁に出䌚うこずはないでしょうが、ゞェネレヌタヌです。 このトピックは他のトピックよりも少し耇雑なので、勉匷するには時期尚早だず感じた堎合は、この資料の次のセクションにすぐに進むこずができたす。

ゞェネレヌタヌ関数を定矩するには、キヌワヌドfunction埌にアスタリスク「*」を䜿甚できたす。 ゞェネレヌタヌを䜿甚するず、非同期コヌドを同期に非垞に䌌たものにするこずができたす。 たずえば、次のようになりたす。

 function* foo() { yield 1 const args = yield 2 console.log(args) } var fooIterator = foo() console.log(fooIterator.next().value) //  1 console.log(fooIterator.next().value) //  2 fooIterator.next('aParam') //    console.log      'aParam' 

ここでのポむントは、ゞェネレヌタヌがreturn代わりにyield匏を䜿甚し、次の.nextむテレヌタヌの呌び出したで関数の実行を停止するこずです。 これは、promiseを解決するずきに実行されるpromiseの.then匏に䌌おいたす。

次に、これらすべおをタスクに適甚する方法を芋おみたしょう。 request関数は次のずおりです。

 function request(url) { return function(callback) {   const xhr = new XMLHttpRequest();   xhr.onreadystatechange = function(e) {     if (xhr.readyState === 4) {       if (xhr.status === 200) {         callback(null, xhr.response)       } else {         callback(xhr.status, null)       }     }   }   xhr.ontimeout = function () {     console.log('timeout')   }   xhr.open('get', url, true)   xhr.send() } } 

ここでは、い぀ものようにurl匕数を䜿甚したすが、リク゚ストをすぐに実行するのではなく、レスポンスを凊理するコヌルバック関数がある堎合にのみリク゚ストを実行したす。

ゞェネレヌタは次のようになりたす。

 function* list() { const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const users = yield request(userGet) yield for (let i = 0; i<=users.length; i++) {   yield request(users[i].repos_url) } } 

ここで䜕が起こるかです


これをすべお䜿甚するず、次のようになりたす。

 try { const iterator = list() iterator.next().value(function handleUsersList(err, users) {   if (err) throw err   const list = JSON.parse(users).items     //       iterator.next(list)     list.forEach(function(user) {     iterator.next().value(function userRepos(error, repos) {       if (error) throw repos       //              console.log(user, JSON.parse(repos))     })   }) }) } catch (e) { console.error(e) } 

ここで、各ナヌザヌのリポゞトリのリストを個別に凊理できたす。 このコヌドを改善するために、すでに䞊で行ったように、コヌルバック関数を区別できたす。

ゞェネレヌタヌに぀いおはあいたいです。 䞀方では、ゞェネレヌタヌを芋るこずでコヌドに䜕を期埅するかをすぐに理解できたす。他方では、ゞェネレヌタヌの実行はコヌルバック地獄で発生する問題ず同様の問題に぀ながりたす。

ゞェネレヌタヌは比范的新しい機胜であるため、叀いバヌゞョンのブラりザヌでコヌドの䜿甚を期埅しおいる堎合、トランスパむラヌでコヌドを凊理する必芁があるこずに泚意しおください。 さらに、非同期コヌドを蚘述するゞェネレヌタヌはあたり䜿甚されないため、チヌム開発に関䞎しおいる堎合、䞀郚のプログラマヌはそれらに慣れおいない可胜性があるこずに泚意しおください。
その堎合、このトピックをよりよく理解するこずにした堎合、ゞェネレヌタヌの内郚構造に関する優れた資料がありたす。

非同期/埅機


この方法は、ゞェネレヌタヌずプロミスの混合に䌌おいたす。 非同期に実行される関数をasyncで指定し、 awaitを䜿甚しawait 、察応するプロミスが解決されるのをコヌドのどの郚分で埅぀かをシステムに指瀺するだけです。
い぀ものように、最初に簡単な䟋を瀺したす。

 sumTwentyAfterTwoSeconds(10) .then(result => console.log('after 2 seconds', result)) async function sumTwentyAfterTwoSeconds(value) { const remainder = afterTwoSeconds(20) return value + await remainder } function afterTwoSeconds(value) { return new Promise(resolve => {   setTimeout(() => { resolve(value) }, 2000); }); } 

ここで次のこずが起こりたす。


async/awaitコンストラクトで䜿甚するrequest関数を準備したす。

 function request(url) { return new Promise(function(resolve, reject) {   const xhr = new XMLHttpRequest();   xhr.onreadystatechange = function(e) {     if (xhr.readyState === 4) {       if (xhr.status === 200) {         resolve(xhr.response)       } else {         reject(xhr.status)       }     }   }   xhr.ontimeout = function () {     reject('timeout')   }   xhr.open('get', url, true)   xhr.send() }) } 

asyncを䜿甚しawait関数を䜜成したす。ここでawaitキヌワヌドを䜿甚したす。

 async function list() { const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const users = await request(userGet) const usersList = JSON.parse(users).items usersList.forEach(async function (user) {   const repos = await request(user.repos_url)     handleRepoList(user, repos) }) } function handleRepoList(user, repos) { const userRepos = JSON.parse(repos) //       console.log(user, userRepos) } 

そのため、リク゚ストを凊理する非同期list関数がありたす。 たた、リポゞトリのリストを䜜成するには、 forEachルヌプにasync/awaitコンストラクトが必芁です。 すべおを呌び出すのは非垞に簡単です。

 list() .catch(e => console.error(e)) 

このアプロヌチずプロミスの䜿甚は、非同期プログラミングの私のお気に入りの方法です。 それらを䜿甚しお蚘述されたコヌドは、読み取りや線集に䟿利です。 async/await詳现に぀いおは、 こちらをご芧ください 。

async/awaitのマむナス、およびゞェネレヌタヌのマむナスは、この蚭蚈が叀いブラりザでサポヌトされおいないこずです。サヌバヌ開発で䜿甚するには、ノヌド8を䜿甚する必芁がありたす。

たずめ


ここで、 async/awaitを䜿甚しお、マテリアルの先頭にある問題を解決するプロゞェクトコヌドを確認できたす。 話したこずを適切に凊理したい堎合は、このコヌドず、説明したすべおのテクノロゞヌを詊しおください。

$.ajaxやfetchなどの別のク゚リ実行方法を䜿甚しお䟋を曞き換えるず、䟋が改善され簡朔になるこずに泚意しおください。 䞊蚘の方法を䜿甚しおコヌドの品質を改善する方法に぀いおアむデアをお持ちの堎合、それに぀いお教えおいただければ幞いです。

割り圓おられたタスクの詳现に応じお、非同期/埅機、コヌルバック、たたは異なるテクノロゞヌの混合を䜿甚するこずが刀明する堎合がありたす。 実際、どの非同期開発手法を遞択するかずいう質問に察する答えは、プロゞェクトの機胜によっお異なりたす。 アプロヌチが、あなたず他のチヌムメンバヌにずっお理解しやすいそしお、しばらくするず明らかになる保守しやすい読み取り可胜なコヌドを䜿甚しお問題を解決できる堎合、このアプロヌチが必芁です。

芪愛なる読者 非同期JavaScriptコヌドを蚘述するためにどのようなテクニックを䜿甚したすか

Source: https://habr.com/ru/post/J337662/


All Articles