JavaScript RemotingとApexの非同期処理の違い
ボタンを押した瞬間に画面をロックせず、サーバー側の処理結果だけを画面に反映する構成を作ります。JavaScript Remotingが実際には何を非同期にしているのか、@future・Queueable・Batch Apexとの違い、ジョブの進捗をAsyncApexJobで照会する方法まで説明します。
この記事で扱う非同期の意味
「非同期処理」という言葉には2つの意味が混ざりがちです。1つはブラウザから見た非同期で、ページを再読み込みせずにサーバーへリクエストを送る仕組みです。もう1つはApexの実行基盤から見た非同期で、リクエストを送った処理とは別のトランザクションでジョブとして動く@future・Queueable Apex・Batch Apexを指します。
この記事のコードは前者です。Visualforce.remoting.Manager.invokeActionというJavaScript Remotingを使い、ボタンを押した後も画面をロックせずに取引先の一覧を取得し、その結果で取引先を更新します。まずこの構成そのものを見て、後半で後者との違いを整理します。
Visualforceページ
<apex:page controller="VisualforcePageAsyncProcessController" apiVersion="67.0" sidebar="false" showHeader="false" standardStylesheets="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0" > <html> <body> <div id="info">Visualforceページ非同期処理</div> <br/> <div id="Size"></div> <div id="State"></div> <script type="module"> document.getElementById("Size").innerText = "件数:取得中"; document.getElementById("State").innerText = "ステータス:開始"; Visualforce.remoting.timeout = 120000; var executedRecords = ''; var cnt = 0; const promiseTest = new Promise((resolve, reject) => { Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.VisualforcePageAsyncProcessController.getRecords}', async(result, event) => { console.log(event.status); if (event.status) { document.getElementById("Size").innerText = "件数:" + result.length; for (const element of result) { cnt = cnt + 1; document.getElementById("State").innerText = "ステータス:処理中"; if (!executedRecords.includes(element)) { executedRecords += element + ","; } } resolve(executedRecords); } else { reject(event); } }); }); promiseTest.then((value) => { document.getElementById("State").innerText = "ステータス:正常終了"; Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.VisualforcePageAsyncProcessController.setRecords}', value, async(result, event) => { if (event.status) { console.log("result:" + result); } }); }, reason => { document.getElementById("State").innerText = "ステータス:異常終了"; console.error(reason); }); </script> </body> </html> </apex:page>
取引先を取得し、取得できたIdをまとめてサーバーへ返す画面です。apex:formを使わず、素のJavaScriptだけでリクエストを送っています。
Visualforce.remoting.timeout = 120000は120秒です。JavaScript Remotingのレスポンスタイムアウトは既定30秒で、延長できる上限が120秒までと公式ドキュメントに明記されています。このコードはちょうどその上限を指定しています。
Apexクラス
public with sharing class VisualforcePageAsyncProcessController { public VisualforcePageAsyncProcessController() { } @RemoteAction public static List<String> getRecords() { List<String> accList = new List<String>(); for (Account acc : [SELECT Id, Name FROM Account LIMIT 10]) { accList.add(acc.Id); } return accList; } @RemoteAction public static void setRecords(String ids) { List<String> idParts = ids.split(','); List<Account> updateAccList = new List<Account>(); for (Account accObj : [SELECT Id, VFProcessDatetime__c FROM Account WHERE Id IN :idParts]) { Account updateAccount = new Account(); updateAccount.Id = accObj.Id; updateAccount.VFProcessDatetime__c = Datetime.now(); updateAccList.add(updateAccount); } Savepoint sp = Database.setSavepoint(); try { if (updateAccList.size() > 0) { update updateAccList; } } catch (DmlException e) { System.debug('The following exception has occurred: ' + e.getMessage()); Database.rollback(sp); } } }
@RemoteActionを付けたメソッドが、JavaScriptから直接呼べるエンドポイントになります。
JavaScript Remotingが非同期にしているもの
JavaScript Remotingが非同期にしているのは、ブラウザとサーバーの間の通信だけです。getRecordsやsetRecords自身は、リクエストを受け取った側で最初から最後まで同期的に実行されます。公式の制約事項にも「JavaScript remoting calls aren't subject to API limits, but Visualforce pages that use JavaScript remoting are subject to all standard Visualforce limits」とあり、通常のVisualforceページと同じガバナ制限の中で動く、と明記されています。
@future・Queueable Apex・Batch Apexが指す「非同期Apex」は、これとは別の話です。呼び出し元のトランザクションとは別に、ジョブとしてキューへ積まれて実行され、AsyncApexJobというレコードとして状態を追いかけられます。
| JavaScript Remoting(このコード) | 非同期Apex(@future・Queueable・Batch) | |
|---|---|---|
| 実行されるタイミング | リクエストを受け取った通常のトランザクション内 | 別のトランザクションのジョブとして |
| ガバナ制限 | 通常のVisualforceページと同じ | ヒープサイズなど一部が緩和される |
| 途中経過の追跡 | レスポンスの中身をJavaScriptで加工するしかない | AsyncApexJobをクエリして進捗を取得できる |
| ページを閉じたら | promiseTestのチェーンが止まり、後続のsetRecordsは呼ばれない | ジョブはキューに残ったまま実行され続ける |
promiseTest.then()は、getRecordsのレスポンスを受け取れて初めてsetRecordsを呼びます。ページを閉じるなどしてレスポンスを受け取れなければ、取引先の更新(setRecords)はそのまま実行されません。進捗を画面へ返したいときの構成
件数が多く、処理そのものを本当にバックグラウンドのジョブにしたい場合は、@RemoteActionの中でQueueable ApexをSystem.enqueueJobで登録し、返ってきたジョブIdを画面へ渡します。
@RemoteAction public static Id startProcess(List<Id> targetIds) { return System.enqueueJob(new AccountUpdateQueueable(targetIds)); }
Queueableのジョブを登録し、ジョブIdを画面へ返す例です。
進捗は、別の@RemoteActionからAsyncApexJobを照会して返します。公式のBatch Apexの解説でも、進捗確認に同じ項目を使ったクエリが例として示されています。
@RemoteAction public static AsyncApexJob checkProgress(Id jobId) { return [ SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems FROM AsyncApexJob WHERE Id = :jobId ]; }
ジョブの状態・処理済み件数・総件数・エラー件数をまとめて取得します。
画面側はapex:actionPollerやsetIntervalでcheckProgressを数秒おきに呼び、Statusが'Completed'になったら表示を切り替えます。この記事の最初のコードのように、JavaScriptだけで結果を組み立ててthenでつなぐ形では、この途中経過は取れません。
apex:actionFunctionとapex:actionStatusという選択肢
このコードは素のJavaScriptでVisualforce.remoting.Manager.invokeActionを直接呼んでいますが、apex:formを使う通常のページなら、apex:actionFunctionでコントローラーのメソッドをJavaScriptから呼び、apex:actionStatusで処理中・完了の表示を自動的に切り替えられます。
<apex:actionFunction name="startProcess" action="{!startProcess}" status="procStatus" reRender="result" /> <apex:actionStatus id="procStatus" startText="処理中です" stopText="完了しました" /> <div id="result"> <apex:outputText value="{!resultText}" /> </div>
フォームを使う一般的なVisualforceページで、進捗表示を宣言的に書く例です。
apex:actionFunctionのstatus属性にapex:actionStatusのidを指定するだけで、リクエストの開始・終了に合わせてstartText・stopTextが自動的に表示されます。このコードのように、innerTextを自分で書き換える処理をJavaScript側に持つ必要がありません。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
| JavaScript Remotingを「非同期Apex」と同じ意味で使う | AsyncApexJobには現れず、ガバナ制限も通常のページと同じままです |
| タイムアウトを120秒より長く設定する | 公式の上限を超えるため、意図通りに延長されません |
promiseTestのチェーンが止まる可能性を考えない | ページを閉じると後続の更新処理が呼ばれないまま終わります |
| 進捗を画面へ返したいのにQueueableへ渡さない | AsyncApexJobを照会する対象がなく、進捗を追跡できません |
確認した環境
- 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、JavaScript Remotingの制約・非同期Apexの種類・AsyncApexJobの照会方法を確認しています
- コードはAPIバージョン67.0で書いています
まとめ
- JavaScript Remotingが非同期にするのはブラウザとサーバーの通信だけで、Apexの処理自体は通常のページと同じガバナ制限の中で同期的に動きます
@future・Queueable・Batch Apexが指す非同期Apexは別のトランザクションのジョブで、AsyncApexJobから状態を照会できます- 進捗を画面へ返したいときは、Queueableのジョブを登録してジョブIdを受け取り、別のリクエストで
AsyncApexJobを照会します apex:formを使う通常のページなら、apex:actionFunctionとapex:actionStatusの組み合わせで進捗表示を宣言的に書けます- JavaScript Remotingのタイムアウトは既定30秒、延長できる上限は120秒です
参考:当時の画面
記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。