В прошлой статье на эту тему (Continuation - что за зверь такой?) мы рассмотрели работу с Continuation из Visualforce страницы, что является привычным и, не побоюсь этого слова,
каноничным вариантом его использования с тех пор, как он стал доступен (в 15м году, если не ошибаюсь).
В релизе Summer’19 была добавлена возможность использовать Continuation и в lightning разработке. В качестве примера возьмём стильный, модный и молодёжный LWC и соорудим вэб-компонент, который будет отправлять асинхронный коллаут на удалённый сервис, ждать ответа 10 секунд и выводить ответ на UI.
Как известно, в LWC апекс метод можно вызвать не только императивно или явно, но и при помощи @wire. Рассмотрим оба варианта. Само собой, то, что к Continuation можно обратиться из лайтнинга, не отменяет того факта, что работа с ним ведётся в апекс коде. Создадим класс:
public with sharing class SampleContinuationClass {
private static final String LONG_RUNNING_SERVICE_URL = 'https://postman-echo.com/delay/10';//сервис, который ответит с задержкой 10 секунд.
//Ссылка на доки (https://docs.postman-echo.com/?version=latest#0189572f-509e-efe0-686d-eed4b3d2f1f0)
// Нет, не надо хардкодить урлы, это просто пример :)
@AuraEnabled(continuation=true)// чтобы отправить Continuation запрос
public static Object startRequestImperative() {
Continuation con = new Continuation(20); // создаём объект и задаёт таймаут
con.continuationMethod = 'processResponseImperative'; // говорим ему в каком методе будет обрабатываться коллбэк
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(LONG_RUNNING_SERVICE_URL);
con.addHttpRequest(req); // отдаём реквест
return con; // объект Continuation нужно вернуть
}
@AuraEnabled
public static Map<String, Object> processResponseImperative(List<String> labels, Object state) {
HttpResponse response = Continuation.getResponse(labels[0]);
return (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
}
}
Где параметр labels - список уникальных лейблов, по одному на каждый запрос (объект Continuation может содержать до 3х запросов).
state - позволяет передать в метод обработки коллбэка какое-нибудь значение, задав его в методе, где формируем объект Continuation. Пример: con.state=‘Hello, World!’;
И для wired вызова добавим пару похожих методов:
@AuraEnabled(continuation=true cacheable=true)
public static Object startRequestWire() {
Continuation con = new Continuation(40);
con.continuationMethod = 'processResponseWire';
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(LONG_RUNNING_SERVICE_URL);
con.addHttpRequest(req);
return con;
}
@AuraEnabled(cacheable=true)
public static Map<String, Object> processResponseWire(List<String> labels, Object state) {
HttpResponse response = Continuation.getResponse(labels[0]);
return (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
}
В разметке нашего компонента разделим wired и imperative по разным lightning-card-ам для наглядности:
<template>
<lightning-layout class="x-large">
<lightning-layout-item padding="around-small">
<lightning-card title="Imperative">
<p class="slds-p-horizontal_small">
<lightning-button label="Call Continuation" onclick={callContinuation}></lightning-button>
Imperative result: {formattedImperativeResult}
</p>
</lightning-card>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-card title="Wire">
<p class="slds-p-horizontal_small">Wired result: {formattedWireResult}</p>
</lightning-card>
</lightning-layout-item>
</lightning-layout>
</template>
И в .js контроллере обратимся к нашим апекс методам по очереди, чтобы увидеть результаты:
import { LightningElement, track, wire } from 'lwc';
import startRequestImperative from '@salesforce/apexContinuation/SampleContinuationClass.startRequestImperative';
import startRequestWire from '@salesforce/apexContinuation/SampleContinuationClass.startRequestWire';
export default class ContinuationComponent extends LightningElement {
@track imperativeContinuation = {};
callContinuation() {// метод вызывается при назажии на кнопку на UI
this.imperativeContinuation = '';
startRequestImperative()
.then(result => {
this.imperativeContinuation = result;
})
.catch(error => {
this.imperativeContinuation = error;
}
);
}
get formattedImperativeResult() {
return JSON.stringify(this.imperativeContinuation);
}
@wire(startRequestWire)
wiredContinuation //и второй вариант
get formattedWireResult() {
return JSON.stringify(
typeof this.wiredContinuation.data !== 'undefined' ? this.wiredContinuation.data : this.wiredContinuation.error
);
}
}
Таким образом мы можем наблюдать, как результат асинхронного вызова отображается на UI после его возвращения от стороннего сервиса.