##// END OF EJS Templates
spelling fixes
andrei -
r19:9d394c2adc2b propose cancellat...
parent child
Show More
@@ -1,257 +1,257
1 1 # Cancellations. Отмена асинхронных операций
2 2
3 3 Использование Promise позволяет организовать обработку результатов работы
4 4 асинхронных фукнций. Ключевые слова async/await позволяют работать с
5 5 асинхронными вызовами в стиле процедурного программирования, хотя по сути это
6 6 событиный подход. При всей своей красоте даннго подхода в нем умышленно
7 7 отсутсвует механизм отмены асинхронной операции, т.е. ее можно начать, но нельзя
8 8 отказаться от результатов ее выполнения, даже если это уже не требуется.
9 9
10 10 Примером того, когда может потребоваться отмена является загрузка большого
11 11 файла, при которой пользователю отображается окно хода операции с возможностью
12 12 ее отмены.
13 13
14 14 ```ts
15 15 // имеется некоторый HTTP клиент
16 16 let client = new HttpClient();
17 17
18 18 // загружается большой файл, с использованием медленного канала
19 19 let data = await client.getJsonAsync('http://host/large-file.json');
20 20
21 21 ```
22 22
23 23 Как поступить в данной ситуации, прежде всего нужно, чтобы сама операция
24 24 поддерживала возможность отмены, предположим, что для этого есть метод
25 25 `client.abort()`.
26 26
27 27 ```ts
28 28
29 29 // имеется некоторый HTTP клиент
30 30 let client = new HttpClient();
31 31
32 // отображаем окно с информацией о хоже операции
32 // отображаем окно с информацией о ходе операции
33 33 let progressView = showProgress("Downloading, please wait...");
34 34
35 35 // код оборачивается в try/finally поскольку созданную форму нужно закрыть
36 36 try {
37 37 // загружается большой файл, с использованием медленного канала
38 38 // здесь, в отличии от предыдущего примера, мы не дожидаемся результата,
39 39 // а запоминаем обещание в переменную downloadTask
40 40 let downloadTask = client.getJsonAsync('http://host/large-file.json');
41 41
42 // связываем нажатие кнопки с отменой загрузки
42 // связываем событие нажатия кнопки с отменой загрузки
43 43 progressView.once('cancel', () => client.abort());
44 44
45 45 // ожидаем окончания загрузки данных
46 46
47 47 let data = await downloadTask;
48 48 } finally {
49 49 // независимот от результата закрываем форму
50 50 // при этом также происходит ануллирование подписок на события
51 51 progressView.close();
52 52 }
53 53
54 54 ```
55 55
56 56 Технические приведенное решение выглядит не плохо, но проблемы появляются, когда
57 57 требуется организовать отмену нескольких операций, особенно если они вложенные.
58 58
59 59 ```ts
60 60 // обновление информации о человеке на форме
61 61 async function updatePersonInfo(info) {
62 62 let client = new RestApiClient();
63 63
64 64 // выплнение нескольких асинхронных операций
65 65 let org = await client.getOrgAsync(info.orgId);
66 66 let city = await client.getCityAsync(info.cityId);
67 67
68 68 // обновление содержимого представления
69 69 renderContent({
70 70 person: info,
71 71 org: org,
72 72 city: city
73 73 });
74 74 }
75 75
76 76 ```
77 77
78 78 Чтобы реализовать возможность отмены такой операции требуется, чтобы в логике
79 79 самой операции была реализована поддержка отмены. Для реализации этого
80 80 потребуется чтобы у операции была информация о запросе отмены, причем данная
81 81 информация относится именно к текущей операции.
82 82
83 Информацию о состоянии запроса на отмену назовет **маркер отмены (cancellation
83 Информацию о состоянии запроса на отмену назовём **маркер отмены (cancellation
84 84 token)**. Поскольку маркер отмены тесно связан с операцией, его удобно
85 85 передавать в виде параметра, тогда код операции будет выглядеть так:
86 86
87 87 ```ts
88 88 // обновление информации о человеке на форме
89 89 // ct - маркер отмены
90 90 async function updatePersonInfo(info, ct) {
91 91 let client = new RestApiClient();
92 92
93 93 // выплнение нескольких асинхронных операций
94 94 // маркер отмены просто передается далее по цепочке вызовов, без
95 95 // дополнительных действий
96 96 let org = await client.getOrg(info.orgId, ct);
97 97 let city = await client.getCity(info.cityId, ct);
98 98
99 99 // обновление содержимого представления
100 100 renderContent({
101 101 person: info,
102 102 org: org,
103 103 city: city
104 104 });
105 105 }
106 106
107 107 ///////////////////////////////////////////////////////////////////////////////
108 108 // ... где-то в другом месте
109 109 ///////////////////////////////////////////////////////////////////////////////
110 110
111 111 // отображаем окно с информацией о хоже операции
112 112 let progressView = showProgress("Loading, please wait...");
113 113
114 114 // создаем маркер отмены для операции на основе события 'cancel'.
115 115 let ct = new Cancellation(cancel => progressView.on('cancel', cancel));
116 116
117 117 // код оборачивается в try/finally поскольку созданную форму нужно закрыть
118 118 try {
119 119 // асинхронно получаем информацию о человеке
120 120 let data = await getPersonInfo(personId, ct);
121 121
122 122 // асинхронно обновляем представление
123 123 await updatePersonInfo(data, ct);
124 124 } finally {
125 125 // независимот от результата закрываем форму
126 126 // при этом также происходит ануллирование подписок на события
127 127 progressView.close();
128 128 }
129 129
130 130 ```
131 131
132 132 Таким образом тот, кто начинает асинхронную операцию заранее определяет как эта
133 133 опреция будет отменена.
134 134
135 135 Важно понимать, что для реализации отмены операции
136 136 могут выделаться ресурсы требующие явного освобождения (DOM, таймеры, события),
137 137 об их освобождении по окончанию операции (успешном или нет) должен позаботиться
138 138 инициатор этой операции. `Cancellation` выступает только в роли посредника для
139 139 доставки события отмены операции до конечного получателя, он не отслеживает и
140 140 не освобождает ресурсы, кроме того, асинхронная операция может его попросту
141 141 проигнорировать.
142 142
143 143 ## `ICancellation` Маркер отмены операции
144 144
145 145 Интерфейс маркера отмены операции. Используется асинхронными операциями, чтобы
146 146 получить оповещение о требуемой отмене.
147 147
148 148 ### `isSupported(): boolean`
149 149
150 150 Определяет, может ли быть запрошена отмена операции через данный маркер.
151 151
152 152 ### `isRequested(): boolean`
153 153
154 154 Возвращает текущее состояние запроса на отмену.
155 155
156 156 ### `throwIfRequested(): void`
157 157
158 158 Если отмена была запрошена, бросает в качестве исключения причину отмены.
159 159
160 160 ### `register(cb: (e:any) => void): IDestroyable`
161 161
162 162 Метод, зарегистрировать обработчик на запрос отмены. Если отмена была запрошена
163 163 зарегистрированный обработчик будет вызван ровно один раз, независимо от того,
164 164 был ли он зарегистрирован до или после запроса отмены.
165 165
166 166 Если отмена уже была запрошена, обработчик будет вызван сразу при регистрации,
167 167 при этом исключения, которые могу возникнуть в обработчике не будут обработаны,
168 168 а передадуться наверх.
169 169
170 170 Вызов данного метода приводит к выделению ресурсов, поэтому операция,
171 171 зарегистрировавшая обработчик должна освободить подписку, которую вернет метод.
172 172
173 173 ```ts
174 174 async function getAsync(url: string, ct: ICancellation = Cancellation.none) {
175 175 // переменная в которой будет запомнена подписка на запрос отмены
176 176 let reg;
177 177 try {
178 178 // оборачиваем операцию загрузки в Promise
179 179 return await new Promise<string>((resolve, reject) => {
180 180 // объект Xhr
181 181 const xhr = new XMLHttpRequest();
182 182 xhr.open("GET", url);
183 183
184 184 // регистрируем обработчики Promise
185 185 xhr.onload = () => resolve(xhr.responseText);
186 186 xhr.onerror = () => reject(xhr.statusText);
187 187
188 188 // отправляем запрос
189 189 xhr.send();
190 190
191 191 // подписываемся на запрос отмены
192 192 reg = ct.register((e) => {
193 193 reject(e);
194 194 xhr.abort();
195 195 });
196 196 });
197 197 } finally {
198 198 if (reg)
199 199 reg.destroy();
200 200 }
201 201 }
202 202
203 203 ```
204 204
205 205 Использование метода `register()` предполагается для организации отмены операций
206 206 не поддерживающих маркеры отмены.
207 207
208 208 ## `Cancellation` Источник отмены
209 209
210 210 Класс используется для создания маркеров отмены. Позволяет создать маркер при
211 211 начале асинхронной операции и связать его, например, с событием DOM.
212 212
213 213 Также маркер можно создавать, когда требуется сложное условие отмены текущей и
214 214 всех нижележещих операций.
215 215
216 216 Как правило в большинстве операций достаточно маркера переданного в параметрах,
217 217 этот же маркер может передаваться ниже.
218 218
219 219 ### `constructor(exec: (cancel: (reason:any) => void ) => void )`
220 220
221 221 Создает новый маркер, при помощи параметра и инициализирует его при помощи
222 222 фукнции, переданной в параметре `exec`.
223 223
224 224 ```ts
225 225
226 226 let htimer;
227 227 let ct = new Cancellation(cancel => {
228 228 htimer = setTimeout(() => cancel("The request is timed out."), 1000);
229 229 });
230 230
231 231 try {
232 232 let text = await getAsync(url, ct);
233 233 } finally {
234 234 // инициатор должен освобождать ресурсы
235 235 // передача недействительного htimer не приводит ни к каким последствиям
236 236 clearTimeout(htimer);
237 237 }
238 238
239 239 ```
240 240
241 241 ## `Cancellation.none: ICancellation`
242 242
243 243 Статическое свойство только для чтения, в котором находится специальный токен
244 244 запроса отмены. Этот токен означает, что отмена никогда не может произойти.
245 245
246 246 Данный токен рекомендуется использовать как значение по-умолчанию для
247 247 параметров, принимающих токен отмены.
248 248
249 249 ```ts
250 250
251 251 async function load(url: string, ct: ICancellation = Cancellation.none) {
252 252 ct.throwIfRequested();
253 253
254 254 // ... the rest of method
255 255 }
256 256
257 257 ``` No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now