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