KASync

async.h
1 /*
2  SPDX-FileCopyrightText: 2014-2015 Daniel Vrátil <[email protected]>
3  SPDX-FileCopyrightText: 2016 Daniel Vrátil <[email protected]>
4  SPDX-FileCopyrightText: 2016 Christian Mollekopf <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #ifndef KASYNC_H
10 #define KASYNC_H
11 
12 #include "kasync_export.h"
13 
14 #include <functional>
15 #include <type_traits>
16 #include <cassert>
17 
18 #include <QVariant>
19 
20 #include "future.h"
21 #include "debug.h"
22 
23 #include "continuations_p.h"
24 #include "executor_p.h"
25 
26 class QObject;
27 
28 /**
29  * @mainpage KAsync
30  *
31  * @brief API to help write async code.
32  *
33  * This API is based around jobs that take lambdas to execute asynchronous tasks.
34  * Each async operation can take a continuation that can then be used to execute
35  * further async operations. That way it is possible to build async chains of
36  * operations that can be stored and executed later on. Jobs can be composed,
37  * similarly to functions.
38  *
39  * Relations between the components:
40  * * Job: API wrapper around Executors chain. Can be destroyed while still running,
41  * because the actual execution happens in the background
42  * * Executor: Describes task to execute. Executors form a linked list matching the
43  * order in which they will be executed. The Executor chain is destroyed when
44  * the parent Job is destroyed. However if the Job is still running it is
45  * guaranteed that the Executor chain will not be destroyed until the execution
46  * is finished.
47  * * Execution: The running execution of the task stored in Executor. Each call to
48  * Job::exec() instantiates new Execution chain, which makes it possible for
49  * the Job to be executed multiple times (even in parallel).
50  * * Future: Representation of the result that is being calculated
51  *
52  *
53  * TODO: Possibility to abort a job through future (perhaps optional?)
54  * TODO: Support for timeout, specified during exec call, after which the error
55  * handler gets called with a defined errorCode.
56  */
57 
58 
59 namespace KAsync {
60 
61 template<typename PrevOut, typename Out, typename ... In>
62 class Executor;
63 
64 class JobBase;
65 
66 template<typename Out, typename ... In>
67 class Job;
68 
69 //@cond PRIVATE
70 namespace Private {
71 
72 template<typename Out, typename ... In>
73 Job<Out, In ...> startImpl(Private::ContinuationHolder<Out, In ...> &&helper)
74 {
75  static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
76  return Job<Out, In...>(QSharedPointer<Private::Executor<Out, In ...>>::create(
77  std::forward<Private::ContinuationHolder<Out, In...>>(helper), nullptr, Private::ExecutionFlag::GoodCase));
78 }
79 
80 } // namespace Private
81 //@endcond
82 
83 
84 /**
85  * @relates Job
86  *
87  * Start an asynchronous job sequence.
88  *
89  * start() is your starting point to build a chain of jobs to be executed
90  * asynchronously.
91  *
92  * @param func A continuation to be executed.
93  */
94 
95 ///Sync continuation without job: [] () -> T { ... }
96 template<typename Out = void, typename ... In, typename F>
97 auto start(F &&func) -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(std::declval<In>() ...))>::value,
98  Job<decltype(func(std::declval<In>() ...)), In...>>
99 {
100  static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
101  return Private::startImpl<Out, In...>(Private::ContinuationHolder<Out, In ...>(SyncContinuation<Out, In ...>(std::forward<F>(func))));
102 }
103 
104 ///continuation with job: [] () -> KAsync::Job<...> { ... }
105 template<typename Out = void, typename ... In, typename F>
106 auto start(F &&func) -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(std::declval<In>() ...))>::value,
107  Job<typename decltype(func(std::declval<In>() ...))::OutType, In...>>
108 {
109  static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
110  return Private::startImpl<Out, In...>(Private::ContinuationHolder<Out, In ...>(JobContinuation<Out, In...>(std::forward<F>(func))));
111 }
112 
113 ///Handle continuation: [] (KAsync::Future<T>, ...) { ... }
114 template<typename Out = void, typename ... In>
115 auto start(AsyncContinuation<Out, In ...> &&func) -> Job<Out, In ...>
116 {
117  static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed.");
118  return Private::startImpl<Out, In...>(Private::ContinuationHolder<Out, In ...>(std::forward<AsyncContinuation<Out, In ...>>(func)));
119 }
120 
121 enum ControlFlowFlag {
122  Break,
123  Continue
124 };
125 
126 /**
127  * @relates Job
128  *
129  * Async while loop.
130  *
131  * Loop continues while body returns ControlFlowFlag::Continue.
132  */
133 KASYNC_EXPORT Job<void> doWhile(const Job<ControlFlowFlag> &body);
134 
135 /**
136  * @relates Job
137  *
138  * Async while loop.
139  *
140  * Shorthand that takes a continuation.
141  *
142  * @see doWhile
143  */
144 KASYNC_EXPORT Job<void> doWhile(const JobContinuation<ControlFlowFlag> &body);
145 
146 
147 
148 /**
149  * @relates Job
150  *
151  * Async delay.
152  */
153 KASYNC_EXPORT Job<void> wait(int delay);
154 
155 /**
156  * @relates Job
157  *
158  * A null job.
159  *
160  * An async noop.
161  *
162  */
163 template<typename Out = void>
164 Job<Out> null();
165 
166 /**
167  * @relates Job
168  *
169  * Async value.
170  */
171 template<typename Out>
172 Job<Out> value(Out);
173 
174 /**
175  * @relates Job
176  *
177  * Async foreach loop.
178  *
179  * This will execute a job for every value in the list.
180  * Errors while not stop processing of other jobs but set an error on the wrapper job.
181  */
182 template<typename List, typename ValueType = typename List::value_type>
184 
185 /**
186  * @relates Job
187  *
188  * Async foreach loop.
189  *
190  * Shorthand that takes a continuation.
191  *
192  * @see serialForEach
193  */
194 template<typename List, typename ValueType = typename List::value_type>
195  Job<void, List> forEach(JobContinuation<void, ValueType> &&);
196 
197 
198 /**
199  * @relates Job
200  *
201  * Serial Async foreach loop.
202  *
203  * This will execute a job for every value in the list sequentially.
204  * Errors while not stop processing of other jobs but set an error on the wrapper job.
205  */
206 template<typename List, typename ValueType = typename List::value_type>
208 
209 /**
210  * @relates Job
211  *
212  * Serial Async foreach loop.
213  *
214  * Shorthand that takes a continuation.
215  *
216  * @see serialForEach
217  */
218 template<typename List, typename ValueType = typename List::value_type>
219 Job<void, List> serialForEach(JobContinuation<void, ValueType> &&);
220 
221 /**
222  * @brief Wait until all given futures are completed.
223  */
224 template<template<typename> class Container>
225 Job<void> waitForCompletion(Container<KAsync::Future<void>> &futures);
226 
227 /**
228  * @relates Job
229  *
230  * An error job.
231  *
232  * An async error.
233  *
234  */
235 template<typename Out = void>
236 Job<Out> error(int errorCode = 1, const QString &errorMessage = QString());
237 
238 /**
239  * @relates Job
240  *
241  * An error job.
242  *
243  * An async error.
244  *
245  */
246 template<typename Out = void>
247 Job<Out> error(const char *);
248 
249 /**
250  * @relates Job
251  *
252  * An error job.
253  *
254  * An async error.
255  *
256  */
257 template<typename Out = void>
258 Job<Out> error(const Error &);
259 
260 //@cond PRIVATE
261 class KASYNC_EXPORT JobBase
262 {
263  template<typename Out, typename ... In>
264  friend class Job;
265 
266 public:
267  explicit JobBase(const Private::ExecutorBasePtr &executor)
268  : mExecutor(executor)
269  {}
270 
271  virtual ~JobBase() = default;
272 
273 protected:
274  Private::ExecutorBasePtr mExecutor;
275 };
276 //@endcond
277 
278 /**
279  * @brief An Asynchronous job
280  *
281  * A single instance of Job represents a single method that will be executed
282  * asynchronously. The Job is started by exec(), which returns Future
283  * immediatelly. The Future will be set to finished state once the asynchronous
284  * task has finished. You can use Future::waitForFinished() to wait for
285  * for the Future in blocking manner.
286  *
287  * It is possible to chain multiple Jobs one after another in different fashion
288  * (sequential, parallel, etc.). Calling exec() will then return a pending
289  * Future, and will execute the entire chain of jobs.
290  *
291  * @code
292  * auto job = Job::start<QList<int>>(
293  * [](KAsync::Future<QList<int>> &future) {
294  * MyREST::PendingUsers *pu = MyREST::requestListOfUsers();
295  * QObject::connect(pu, &PendingOperation::finished,
296  * [&](PendingOperation *pu) {
297  * future->setValue(dynamic_cast<MyREST::PendingUsers*>(pu)->userIds());
298  * future->setFinished();
299  * });
300  * })
301  * .each<QList<MyREST::User>, int>(
302  * [](const int &userId, KAsync::Future<QList<MyREST::User>> &future) {
303  * MyREST::PendingUser *pu = MyREST::requestUserDetails(userId);
304  * QObject::connect(pu, &PendingOperation::finished,
305  * [&](PendingOperation *pu) {
306  * future->setValue(Qlist<MyREST::User>() << dynamic_cast<MyREST::PendingUser*>(pu)->user());
307  * future->setFinished();
308  * });
309  * });
310  *
311  * KAsync::Future<QList<MyREST::User>> usersFuture = job.exec();
312  * usersFuture.waitForFinished();
313  * QList<MyRest::User> users = usersFuture.value();
314  * @endcode
315  *
316  * In the example above, calling @p job.exec() will first invoke the first job,
317  * which will retrieve a list of IDs and then will invoke the second function
318  * for each single entry in the list returned by the first function.
319  */
320 template<typename Out, typename ... In>
321 class [[nodiscard]] Job : public JobBase
322 {
323  //@cond PRIVATE
324  template<typename OutOther, typename ... InOther>
325  friend class Job;
326 
327  template<typename OutOther, typename ... InOther>
328  friend Job<OutOther, InOther ...> Private::startImpl(Private::ContinuationHolder<OutOther, InOther ...> &&);
329 
330  template<typename List, typename ValueType>
331  friend Job<void, List> forEach(KAsync::Job<void, ValueType> job);
332 
333  template<typename List, typename ValueType>
334  friend Job<void, List> serialForEach(KAsync::Job<void, ValueType> job);
335 
336  // Used to disable implicit conversion of Job<void to Job<void> which triggers
337  // comiler warning.
338  struct IncompleteType;
339  //@endcond
340 
341 public:
342  typedef Out OutType;
343 
344  ///A continuation
345  template<typename OutOther, typename ... InOther>
346  Job<OutOther, In ...> then(const Job<OutOther, InOther ...> &job) const;
347 
348  ///Shorthands for a job that returns another job from it's continuation
349  //
350  //OutOther and InOther are only there fore backwards compatibility, but are otherwise ignored.
351  //It should never be neccessary to specify any template arguments, as they are automatically deduced from the provided argument.
352  //
353  //We currently have to write a then overload for:
354  //* One argument in the continuation
355  //* No argument in the continuation
356  //* One argument + error in the continuation
357  //* No argument + error in the continuation
358  //This is due to how we extract the return type with "decltype(func(std::declval<Out>()))".
359  //Ideally we could conflate this into at least fewer overloads, but I didn't manage so far and this at least works as expected.
360 
361  ///Continuation returning job: [] (Arg) -> KAsync::Job<...> { ... }
362  template<typename OutOther = void, typename ... InOther, typename F>
363  auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(std::declval<Out>()))>::value,
364  Job<typename decltype(func(std::declval<Out>()))::OutType, In...>>
365  {
366  using ResultJob = decltype(func(std::declval<Out>())); //Job<QString, int>
367  return thenImpl<typename ResultJob::OutType, Out>(
368  {JobContinuation<typename ResultJob::OutType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
369  }
370 
371  ///Void continuation with job: [] () -> KAsync::Job<...> { ... }
372  template<typename OutOther = void, typename ... InOther, typename F>
373  auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func())>::value,
374  Job<typename decltype(func())::OutType, In...>>
375  {
376  using ResultJob = decltype(func()); //Job<QString, void>
377  return thenImpl<typename ResultJob::OutType>(
378  {JobContinuation<typename ResultJob::OutType>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
379  }
380 
381  ///Error continuation returning job: [] (KAsync::Error, Arg) -> KAsync::Job<...> { ... }
382  template<typename OutOther = void, typename ... InOther, typename F>
383  auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(KAsync::Error{}, std::declval<Out>()))>::value,
384  Job<typename decltype(func(KAsync::Error{}, std::declval<Out>()))::OutType, In...>>
385  {
386  using ResultJob = decltype(func(KAsync::Error{}, std::declval<Out>())); //Job<QString, int>
387  return thenImpl<typename ResultJob::OutType, Out>(
388  {JobErrorContinuation<typename ResultJob::OutType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
389  }
390 
391  ///Error void continuation returning job: [] (KAsync::Error) -> KAsync::Job<...> { ... }
392  template<typename OutOther = void, typename ... InOther, typename F>
393  auto then(F &&func) const -> std::enable_if_t<std::is_base_of<JobBase, decltype(func(KAsync::Error{}))>::value,
394  Job<typename decltype(func(KAsync::Error{}))::OutType, In...>>
395  {
396  using ResultJob = decltype(func(KAsync::Error{}));
397  return thenImpl<typename ResultJob::OutType>(
398  {JobErrorContinuation<typename ResultJob::OutType>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
399  }
400 
401  ///Sync continuation: [] (Arg) -> void { ... }
402  template<typename OutOther = void, typename ... InOther, typename F>
403  auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(std::declval<Out>()))>::value,
404  Job<decltype(func(std::declval<Out>())), In...>>
405  {
406  using ResultType = decltype(func(std::declval<Out>())); //QString
407  return thenImpl<ResultType, Out>(
408  {SyncContinuation<ResultType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
409  }
410 
411  ///Sync void continuation: [] () -> void { ... }
412  template<typename OutOther = void, typename ... InOther, typename F>
413  auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func())>::value,
414  Job<decltype(func()), In...>>
415  {
416  using ResultType = decltype(func()); //QString
417  return thenImpl<ResultType>(
418  {SyncContinuation<ResultType>(std::forward<F>(func))}, Private::ExecutionFlag::GoodCase);
419  }
420 
421  ///Sync error continuation: [] (KAsync::Error, Arg) -> void { ... }
422  template<typename OutOther = void, typename ... InOther, typename F>
423  auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(KAsync::Error{}, std::declval<Out>()))>::value,
424  Job<decltype(func(KAsync::Error{}, std::declval<Out>())),In...>>
425  {
426  using ResultType = decltype(func(KAsync::Error{}, std::declval<Out>())); //QString
427  return thenImpl<ResultType, Out>(
428  {SyncErrorContinuation<ResultType, Out>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
429  }
430 
431  ///Sync void error continuation: [] (KAsync::Error) -> void { ... }
432  template<typename OutOther = void, typename ... InOther, typename F>
433  auto then(F &&func) const -> std::enable_if_t<!std::is_base_of<JobBase, decltype(func(KAsync::Error{}))>::value,
434  Job<decltype(func(KAsync::Error{})), In...>>
435  {
436  using ResultType = decltype(func(KAsync::Error{}));
437  return thenImpl<ResultType>(
438  {SyncErrorContinuation<ResultType>(std::forward<F>(func))}, Private::ExecutionFlag::Always);
439  }
440 
441  ///Shorthand for a job that receives the error and a handle
442  template<typename OutOther, typename ... InOther>
443  Job<OutOther, In ...> then(AsyncContinuation<OutOther, InOther ...> &&func) const
444  {
445  return thenImpl<OutOther, InOther ...>({std::forward<AsyncContinuation<OutOther, InOther ...>>(func)},
446  Private::ExecutionFlag::GoodCase);
447  }
448 
449  ///Shorthand for a job that receives the error and a handle
450  template<typename OutOther, typename ... InOther>
451  Job<OutOther, In ...> then(AsyncErrorContinuation<OutOther, InOther ...> &&func) const
452  {
453  return thenImpl<OutOther, InOther ...>({std::forward<AsyncErrorContinuation<OutOther, InOther ...>>(func)}, Private::ExecutionFlag::Always);
454  }
455 
456  ///Shorthand for a job that receives the error only
457  Job<Out, In ...> onError(SyncErrorContinuation<void> &&errorFunc) const;
458 
459  /**
460  * Shorthand for a forEach loop that automatically uses the return type of
461  * this job to deduce the type exepected.
462  */
463  template<typename OutOther = void, typename ListType = Out, typename ValueType = typename ListType::value_type, std::enable_if_t<!std::is_void<ListType>::value, int> = 0>
464  Job<void, In ...> each(JobContinuation<void, ValueType> &&func) const
465  {
466  eachInvariants<OutOther>();
467  return then<void, In ...>(forEach<Out, ValueType>(std::forward<JobContinuation<void, ValueType>>(func)));
468  }
469 
470  /**
471  * Shorthand for a serialForEach loop that automatically uses the return type
472  * of this job to deduce the type exepected.
473  */
474  template<typename OutOther = void, typename ListType = Out, typename ValueType = typename ListType::value_type, std::enable_if_t<!std::is_void<ListType>::value, int> = 0>
475  Job<void, In ...> serialEach(JobContinuation<void, ValueType> &&func) const
476  {
477  eachInvariants<OutOther>();
478  return then<void, In ...>(serialForEach<Out, ValueType>(std::forward<JobContinuation<void, ValueType>>(func)));
479  }
480 
481  /**
482  * Enable implicit conversion to Job<void>.
483  *
484  * This is necessary in assignments that only use the return value (which is the normal case).
485  * This avoids constructs like:
486  * auto job = KAsync::start<int>( ... )
487  * .then<void, int>( ... )
488  * .then<void>([](){}); //Necessary for the assignment without the implicit conversion
489  */
490  template<typename ... InOther>
491  operator std::conditional_t<std::is_void<OutType>::value, IncompleteType, Job<void>>();
492 
493  /**
494  * Adds an unnamed value to the context.
495  * The context is guaranteed to persist until the jobs execution has finished.
496  *
497  * Useful for setting smart pointer to manage lifetime of objects required
498  * during the execution of the job.
499  */
500  template<typename T>
501  Job<Out, In ...> &addToContext(const T &value)
502  {
503  assert(mExecutor);
504  mExecutor->addToContext(QVariant::fromValue<T>(value));
505  return *this;
506  }
507 
508  /**
509  * Adds a guard.
510  * It is guaranteed that no callback is executed after the guard vanishes.
511  *
512  * Use this i.e. ensure you don't call-back into an already destroyed object.
513  */
514  Job<Out, In ...> &guard(const QObject *o)
515  {
516  assert(mExecutor);
517  mExecutor->guard(o);
518  return *this;
519  }
520 
521  /**
522  * @brief Starts execution of the job chain.
523  *
524  * This will start the execution of the task chain, starting from the
525  * first one. It is possible to call this function multiple times, each
526  * invocation will start a new processing and provide a new Future to
527  * watch its status.
528  *
529  * @param in Argument to be passed to the very first task
530  * @return Future&lt;Out&gt; object which will contain result of the last
531  * task once if finishes executing. See Future documentation for more details.
532  *
533  * @see exec(), Future
534  */
535  template<typename FirstIn>
536  KAsync::Future<Out> exec(FirstIn in);
537 
538  /**
539  * @brief Starts execution of the job chain.
540  *
541  * This will start the execution of the task chain, starting from the
542  * first one. It is possible to call this function multiple times, each
543  * invocation will start a new processing and provide a new Future to
544  * watch its status.
545  *
546  * @return Future&lt;Out&gt; object which will contain result of the last
547  * task once if finishes executing. See Future documentation for more details.
548  *
549  * @see exec(FirstIn in), Future
550  */
551  KAsync::Future<Out> exec();
552 
553  explicit Job(JobContinuation<Out, In ...> &&func);
554  explicit Job(AsyncContinuation<Out, In ...> &&func);
555 
556 private:
557  //@cond PRIVATE
558  explicit Job(Private::ExecutorBasePtr executor);
559 
560  template<typename OutOther, typename ... InOther>
561  Job<OutOther, In ...> thenImpl(Private::ContinuationHolder<OutOther, InOther ...> helper,
562  Private::ExecutionFlag execFlag = Private::ExecutionFlag::GoodCase) const;
563 
564  template<typename InOther, typename ... InOtherTail>
565  void thenInvariants() const;
566 
567  //Base case for an empty parameter pack
568  template<typename ... InOther>
569  auto thenInvariants() const -> std::enable_if_t<(sizeof...(InOther) == 0)>;
570 
571  template<typename OutOther>
572  void eachInvariants() const;
573  //@endcond
574 };
575 
576 } // namespace KAsync
577 
578 
579 // out-of-line definitions of Job methods
580 #include "job_impl.h"
581 
582 #endif // KASYNC_H
Job< void, In... > each(JobContinuation< void, ValueType > &&func) const
Shorthand for a forEach loop that automatically uses the return type of this job to deduce the type e...
Definition: async.h:464
An Asynchronous job.
Definition: async.h:67
auto then(F &&func) const -> std::enable_if_t< std::is_base_of< JobBase, decltype(func(std::declval< Out >()))>::value, Job< typename decltype(func(std::declval< Out >()))::OutType, In... >>
Shorthands for a job that returns another job from it&#39;s continuation.
Definition: async.h:363
STL namespace.
auto start(F &&func) -> std::enable_if_t< std::is_base_of< JobBase, decltype(func(std::declval< In >()...))>::value, Job< typename decltype(func(std::declval< In >()...))::OutType, In... >>
continuation with job: [] () -> KAsync::Job<...> { ... }
Definition: async.h:106
Job< void, In... > serialEach(JobContinuation< void, ValueType > &&func) const
Shorthand for a serialForEach loop that automatically uses the return type of this job to deduce the ...
Definition: async.h:475
auto then(F &&func) const -> std::enable_if_t< std::is_base_of< JobBase, decltype(func())>::value, Job< typename decltype(func())::OutType, In... >>
Void continuation with job: [] () -> KAsync::Job<...> { ... }.
Definition: async.h:373
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
auto then(F &&func) const -> std::enable_if_t<!std::is_base_of< JobBase, decltype(func(std::declval< Out >()))>::value, Job< decltype(func(std::declval< Out >())), In... >>
Sync continuation: [] (Arg) -> void { ... }.
Definition: async.h:403
Job< Out, In... > & guard(const QObject *o)
Adds a guard.
Definition: async.h:514
Job< Out, In... > & addToContext(const T &value)
Adds an unnamed value to the context.
Definition: async.h:501
Future is a promise that is used by Job to deliver result of an asynchronous execution.
Definition: future.h:185
Definition: async.h:59
auto start(F &&func) -> std::enable_if_t<!std::is_base_of< JobBase, decltype(func(std::declval< In >()...))>::value, Job< decltype(func(std::declval< In >()...)), In... >>
Definition: async.h:97
Job< OutOther, In... > then(AsyncErrorContinuation< OutOther, InOther... > &&func) const
Shorthand for a job that receives the error and a handle.
Definition: async.h:451
Job< OutOther, In... > then(AsyncContinuation< OutOther, InOther... > &&func) const
Shorthand for a job that receives the error and a handle.
Definition: async.h:443
auto then(F &&func) const -> std::enable_if_t<!std::is_base_of< JobBase, decltype(func())>::value, Job< decltype(func()), In... >>
Sync void continuation: [] () -> void { ... }.
Definition: async.h:413
auto then(F &&func) const -> std::enable_if_t<!std::is_base_of< JobBase, decltype(func(KAsync::Error
Sync error continuation: [] (KAsync::Error, Arg) -> void { ... }.
Definition: async.h:423
A specialization of Future<T> for tasks that have no (void) result.
Definition: future.h:375
auto then(F &&func) const -> std::enable_if_t< std::is_base_of< JobBase, decltype(func(KAsync::Error
Error continuation returning job: [] (KAsync::Error, Arg) -> KAsync::Job<...> { ... }.
Definition: async.h:383
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Apr 11 2021 23:07:13 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.