В качестве языка (и среды выполнения) javascript принципиально однопоточен. Это означает, что любой запрос блокировки или вычисление блокируют всю страницу (а в старых браузерах само программное обеспечение даже не позволяет пользователям переключаться на другую вкладку): среду javascript можно рассматривать как основанную на событиях runloop, где разработчики приложений не могут контролировать Над самим рундуком.
Т.к. выполнение длительных синхронных запросов или других типов сложных и ресурсоемких обращений не желательно, то вместо них рациональней использовать асинхронные API.
Цель этого руководства - предоставить некоторые инструменты для работы с асинхронными системами и предостеречь от системных проблем.
Отложенные вызовы (Deferreds)
Отложенные вызовы - это форма promises (обещаний). В Odoo Web в настоящее время используется jQuery's deferred.
Основная идея отложенных вызовов заключается в том, что потенциально асинхронные методы возвращают объект Deferred()
вместо произвольного значения или (чаще всего) ничего.
Этот объект затем можно использовать для отслеживания конца асинхронной операции путем добавления к нему обратных вызовов, либо обратных вызовов успешного выполнения, либо обратных вызовов ошибок.
Огромным преимуществом отложенных вызовов в сравнении с обратным вызовов функции асинхронными методами является возможность задать очередность запуска.
Использование отложенных вызовов
Наиболее важный метод отсроченных вызовов Deferred.then()
. Он используется для присоединения новых обратных вызовов к отложенному объекту.
Первый параметр присоединяет обратный вызов успеха, который вызывается, когда отложенный объект успешно решен и предоставляется разрешенное значение для асинхронной операции.
Второй параметр присоединяет обратный вызов неудачи, который вызывается, когда отложенный объект отклоняется и снабжается значениями отклонения (часто неким сообщением об ошибке).
Обратные вызовы, привязанные к отложенным вызовам, никогда не теряются: если обратный вызов присоединен к уже разрешенному или отклоненному отложенному вызову, обратный вызов будет вызван (или проигнорирован) немедленно. Отложенный вызов может разрешиться или отклониться один раз и либо разрешается, либо отклоняется: данный отложенный вызов не может дважды вызвать один обратный вызов успеха или вызвать обратные вызовы как с успехом, так и с ошибкой.
then()
это метод, который вы будете использовать чаще всего при взаимодействии с отложенными объектами (и, следовательно, асинхронными API).
Создание отложенных вызовов
After using asynchronous APIs may come the time to build them: for mocks, to compose deferreds from multiple source in a complex manner, in order to let the current operations repaint the screen or give other events the time to unfold, ...
This is easy using jQuery's deferred objects.
Примечание
this section is an implementation detail of jQuery Deferred objects, the creation of promises is not part of any standard (even tentative) that I know of. If you are using deferred objects which are not jQuery's, their API may (and often will) be completely different.
Отложенные запросы создаются путем вызова их конструктора 1 без каких-либо аргументов. Это создает экземпляр объекта Deferred()
с помощью следующих методов:
В соответствии со своим именем, этот метод переносит отложенный вызов в состояние «Разрешено». Ему может быть предоставлено столько аргументов, сколько необходимо, эти аргументы будут предоставлены любому методу, ожидающему обратного вызова.
Аналогично
resolve()
, но переносит отложенный вызов в состояние «Отклонено» и вызывает отложенные обработчики ошибок.
Creates a readonly view of the deferred object. It is generally a good idea to return a promise view of the deferred to prevent callers from resolving or rejecting the deferred in your stead.
reject()
and resolve()
are used
to inform callers that the asynchronous operation has failed (or
succeeded). These methods should simply be called when the
asynchronous operation has ended, to notify anybody interested in its
result(s).
Composing deferreds
What we've seen so far is pretty nice, but mostly doable by passing functions to other functions (well adding functions post-facto would probably be a chore... still, doable).
Deferreds truly shine when code needs to compose asynchronous operations in some way or other, as they can be used as a basis for such composition.
There are two main forms of compositions over deferred: multiplexing and piping/cascading.
Deferred multiplexing
The most common reason for multiplexing deferred is simply performing multiple asynchronous operations and wanting to wait until all of them are done before moving on (and executing more stuff).
The jQuery multiplexing function for promises is when()
.
Примечание
the multiplexing behavior of jQuery's when()
is an
(incompatible, mostly) extension of the behavior defined in
CommonJS Promises/B.
This function can take any number of promises 2 and will return a promise.
The returned promise will be resolved when all multiplexed promises
are resolved, and will be rejected as soon as one of the multiplexed
promises is rejected (it behaves like Python's all()
, but with
promise objects instead of boolean-ish).
The resolved values of the various promises multiplexed via
when()
are mapped to the arguments of when()
's
success callback, if they are needed. The resolved values of a promise
are at the same index in the callback's arguments as the promise in
the when()
call so you will have:
$.when(p0, p1, p2, p3).then(
function (results0, results1, results2, results3) {
// code
});
Предупреждение
in a normal mapping, each parameter to the callback would be an
array: each promise is conceptually resolved with an array of 0..n
values and these values are passed to when()
's
callback. But jQuery treats deferreds resolving a single value
specially, and "unwraps" that value.
For instance, in the code block above if the index of each promise
is the number of values it resolves (0 to 3), results0
is an
empty array, results2
is an array of 2 elements (a pair) but
results1
is the actual value resolved by p1
, not an array.
Deferred chaining
A second useful composition is starting an asynchronous operation as the result of an other asynchronous operation, and wanting the result of both: with the tools described so far, handling e.g. OpenERP's search/read sequence with this would require something along the lines of:
var result = $.Deferred();
Model.search(condition).then(function (ids) {
Model.read(ids, fields).then(function (records) {
result.resolve(records);
});
});
return result.promise();
While it doesn't look too bad for trivial code, this quickly gets unwieldy.
But then()
also allows handling this kind of
chains: it returns a new promise object, not the one it was called
with, and the return values of the callbacks is important to this behavior:
whichever callback is called,
- If the callback is not set (not provided or left to null), the
resolution or rejection value(s) is simply forwarded to
then()
's promise (it's essentially a noop) If the callback is set and does not return an observable object (a deferred or a promise), the value it returns (
undefined
if it does not return anything) will replace the value it was given, e.g.promise.then(function () { console.log('called'); });
will resolve with the sole value
undefined
.If the callback is set and returns an observable object, that object will be the actual resolution (and result) of the pipe. This means a resolved promise from the failure callback will resolve the pipe, and a failure promise from the success callback will reject the pipe.
This provides an easy way to chain operation successes, and the previous piece of code can now be rewritten:
return Model.search(condition).then(function (ids) { return Model.read(ids, fields); });
the result of the whole expression will encode failure if either
search
orread
fails (with the right rejection values), and will be resolved withread
's resolution values if the chain executes correctly.
then()
is also useful to adapt third-party
promise-based APIs, in order to filter their resolution value counts
for instance (to take advantage of when()
's special
treatment of single-value promises).
jQuery.Deferred API
when(deferreds…)
- deferreds -- deferred objects to multiplex
class Deferred()
Deferred.then(doneCallback[, failCallback])
Attaches new callbacks to the resolution or rejection of the deferred object. Callbacks are executed in the order they are attached to the deferred.
To provide only a failure callback, pass null
as the
doneCallback
, to provide only a success callback the
second argument can just be ignored (and not passed at all).
Returns a new deferred which resolves to the result of the corresponding callback, if a callback returns a deferred itself that new deferred will be used as the resolution of the chain.
- doneCallback -- function called when the deferred is resolved
- failCallback -- function called when the deferred is rejected
Deferred.done(doneCallback)
Attaches a new success callback to the deferred, shortcut for
deferred.then(doneCallback)
.
Примечание
a difference is the result of Deferred.done()
's
is ignored rather than forwarded through the chain
This is a jQuery extension to CommonJS Promises/A providing
little value over calling then()
directly,
it should be avoided.
- doneCallback (
Function
) -- function called when the deferred is resolved
Deferred.fail(failCallback)
Attaches a new failure callback to the deferred, shortcut for
deferred.then(null, failCallback)
.
A second jQuery extension to Promises/A. Although it provides more value than
done()
, it still is not much and should be
avoided as well.
- failCallback (
Function
) -- function called when the deferred is rejected
Deferred.promise()
Returns a read-only view of the deferred object, with all mutators (resolve and reject) methods removed.
Deferred.resolve(value…)
Called to resolve a deferred, any value provided will be passed onto the success handlers of the deferred object.
Resolving a deferred which has already been resolved or rejected has no effect.
Deferred.reject(value…)
Called to reject (fail) a deferred, any value provided will be passed onto the failure handler of the deferred object.
Rejecting a deferred which has already been resolved or rejected has no effect.
Deferred()
as a function, the
result is the sameor not-promises, the CommonJS Promises/B role of
when()
is to be able to treat values and promises
uniformly: when()
will pass promises through directly,
but non-promise values and objects will be transformed into a
resolved promise (resolving themselves with the value itself).
jQuery's when()
keeps this behavior making deferreds
easy to build from "static" values, or allowing defensive code
where expected promises are wrapped in when()
just in
case.