├── .gitmodules ├── LICENSE ├── README.md ├── promise.hpp └── tests ├── CMakeLists.txt ├── Makefile ├── README.md └── promise-test.cpp /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "async-test"] 2 | path = tests/async-test 3 | url = git@github.com:alxvasilev/async-test.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2014, MEGA Limited 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript-like C++ promise library 2 | 3 | This library provides a `Promise` class that aims to mimic the behavior of the typical 4 | JavaScript promise object. It does not implement any particular Javascript standard promise 5 | API (Promises/A, native promise, etc), but follows the main principles. This document assumes that 6 | the reader has a basic understanding of how typical Javascript promises work. 7 | 8 | IMPORTANT NOTE: there is one major difference, though. Most modern Javascript promises (including JS Native promises) resolve asynchronously, i.e. their `resolve()` method does not directly call the `then()` handlers, 9 | but schedules the calls on the next message loop iteration. The same happens when a `then()/catch()` handler 10 | is attached to an already resolved/rejected promise. This may be a bit less efficient, but makes the behavior symmetric and more predictable. This library resolves synchronously, because it is unaware of the 11 | message loop that is used in the application. 12 | 13 | ## Installation 14 | You need to clone this repository with the `--recursive` switch if you want to run the tests. This is because the test framework - `async-test`, is included as a submodule. 15 | 16 | ## Usage 17 | This is a header-only library, consisting of just one header - promise.hpp. You only need to include that header in your code. There are no dependencies on other files from this repository, so you can copy promise.hpp to your project tree and use it from there. 18 | 19 | ## Compatibility 20 | This library should be compatible with gcc >= 4.9, clang >= 3.3 and Visual Studio >= 2015 21 | 22 | ## Tests 23 | A test suite is included, together with a submodule for the `async-test` framework that is used for the test suite. See the Readme.md in the `tests` subdirectory for details how to run and build the tests. 24 | 25 | # API 26 | A brief overview of the API follows. For practical usage examples and details on the behavior, 27 | please see the included tests in `tests/promise-test.cpp` 28 | 29 | ## The Promise class 30 | Intuitively, the `T` class is the type of the value, held by the promise object. It can be `void` as well. 31 | 32 | As for what `L` is - some explanation is needed. When `.then()` and `.fail()` handlers 33 | are attached to a promise, they are added to internal lists. For performance reasons, 34 | these lists are implemented as static arrays. The `L` constant is the size of 35 | these arrays, and its default value is 4. This avoids some dynamic memory allocation and deallocation 36 | when promise objects are created and destroyed. This comes at the cost of having a fixed limit of 37 | `.then` and `.fail` handlers that can be attached to a _single_ promise object. 38 | Note that this does not affect the promise chain length, but only the number of chains a promise can 39 | "fork". Also, when a `.then()` or `.fail()` callback is executed, the actual promise that it returns 40 | is "merged" with the "placeholder" promise that was returned by the `.then()` / `.fail()` method 41 | at the time the promise chain was set up (usually before anything has yet executed). 42 | This "merge" operation moves all handlers, that may be attached to the returned promise, 43 | to the "placeholder", i.e. chain linking promise. 44 | Therefore, there need to be enough slots in that chain linking promise. This is usually not an issue, 45 | because the promises returned by handlers don't have any handlers attached. Handlers are usually 46 | attached when chaining promises. This, however, may not be the case, if the handler itself contains 47 | a promise chain, and returns a promise that is not at its end. This is a very exotic case, and is still 48 | perfectly fine with a reasonable number of handlers attached to each of the two promises. In any case, 49 | checks are performed in both debug and release mode and an exception is thrown if callback slots are 50 | exhausted. The exception is of type `std::runtime_error` and has an informative message. Please let me know 51 | if the fixed maximum of handlers is a problem for you. If it turns our to be cumbersome for many users, 52 | I will consider switching to dynamic lists. 53 | You can increase the default globally by defining `PROMISE_MAX_HANDLE_COUNT` before including `promise.hpp`. However, this define-before-include order has to be taken care of for each compilation unit. 54 | This may be cumbersome, if done at the source code level. A better option could be to add the define to the 55 | build system of your application, so that all compilation units will have it specified, and it will always be defined before any code is preprocessed/compiled. 56 | You can also define it per object by overriding the template parameter. This can be done in specific 57 | use cases where it is known that a lot of handlers will be attached to that promise object. However, this is 58 | currently not well supported, since L is not passed to the promise types returned by `.then()`, `.fail()`, etc. 59 | This may lead to compile errors. 60 | 61 | ### Lifetime of Promise objects 62 | The Promise object is internally reference counted, so copying it is very lightweight and its lifetime is 63 | automatically managed. Normally you don't need to create Promise objects on the heap with `operator new`, and 64 | pass them by pointers. They are designed to be allocated on the stack or as a member. 65 | You can regard the Promise class as a fancy shared pointer. 66 | 67 | ### Error class 68 | Error is a special class that carries information about an error. It has a type code, error code and an 69 | optional message string. It is reference-counted, so it's very lightweight to copy around and 70 | its lifetime is automatically managed. 71 | If a particular Error object is not passed to a `fail()` handler during its lifetime, 72 | an "unhandled promise error" warning will be printed to `stderr`. The user can configure the library 73 | to call a function instead, by defining the macro `PROMISE_ON_UNHANDLED_ERROR` to the name of the function 74 | to be called. The function has the following prototype: `myHandler(const std::string& msg, int type, int code)`. 75 | In this case, `PROMISE_ON_UNHANDLED_ERROR` has to be defined as: 76 | 77 | `#define PROMISE_ON_UNHANDLED_ERROR myHandler` 78 | 79 | The handler function doesn't need to be aware of the Promise library - this is the reason its prototype is specified to not take the Error object directly. 80 | 81 | ### `Promise::resolve(T val)` 82 | ### `Promise::resolve()` 83 | Resolves the promise and causes `.then()` handlers to be executed down the promise chain. 84 | 85 | ### `Promise::reject(Error err)` 86 | Rejects the promise and causes `.fail()` handlers to be executed down the promise chain. 87 | 88 | 89 | ### `Promise::then(F&& func)` 90 | The provided functor is called with the value of the promise, when the promise is 91 | resolved. The return value of `then()` is a `Promise` object of the type, returned 92 | by the functor. If the functor returns a promise, the return type of the type of that promise. 93 | 94 | ### `Promise::fail(F&& func)` 95 | The provided functor is called with an Error object. The return value is a promise of the same type as the 96 | one on which `fail()` is called. If the functor returns a promise that is eventually rejected, further error handlers down the chain will be called, if any. If there are none, a warning will be printed on stdout (see above). If the returned promise is eventually resolved, further `.then()` handlers down the chain will be executed. 97 | 98 | ### `Promise::done()` 99 | Returns the state of the promise, as one of the three values of the `ResolvedState` enum - pending, succeeded or 100 | failed. The pending state `kNotResolved` has the value of zero, so the value can be conveniently used in boolean 101 | expressions to signify whether the promise is is pending or resolved/failed, i.e. "done". 102 | 103 | ### `Promise::succeeded(), Promise::failed()` 104 | Convenience methods to check the state of the promise. 105 | 106 | ### `Promise::error()` 107 | Returns the Error object with which the promise was rejected. If the promise is not in `kRejected` state, an assertion is triggered. Therefore, this method should only be called after a check if the promise is actually 108 | in rejected state. 109 | 110 | ### `static Promise Promise::when(...)` 111 | ### `static Promise Promise::when(std::vector>)` 112 | Returns a Promise that is: 113 | - Resolved when all the provided promises are resolved. 114 | - Rejected if at least one of the provided promises is rejected. 115 | The difference between the two methods is that the one that takes multiple arguments can take promises of different 116 | types, where as the one that takes a vector operates on promises of the same type. 117 | 118 | 119 | -------------------------------------------------------------------------------- /promise.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | @author Alexander Vassilev 3 | @copyright MEGA Limited, 2014 4 | @license BSD 2-Clause "Simplified" License 5 | */ 6 | 7 | #ifndef _PROMISE_HPP 8 | #define _PROMISE_HPP 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifndef PROMISE_MAX_HANDLER_COUNT 17 | #define PROMISE_MAX_HANDLER_COUNT 4 18 | #endif 19 | 20 | /** @brief The name of the unhandled promise error handler. This handler is 21 | * called when a promise fails, but the user has not provided a fail() callback 22 | * to handle that error. Often this is unintentional and results in the program 23 | * stepping execution for no obvious reason. 24 | * The user can define this to customize the unhandled error trap. 25 | * The default just prints a warning to stderr 26 | */ 27 | #ifndef PROMISE_ON_UNHANDLED_ERROR 28 | #define PROMISE_ON_UNHANDLED_ERROR ErrorShared::defaultOnUnhandledError 29 | #else // define the prototype, as it may come after the inclusion of promise.h 30 | void PROMISE_ON_UNHANDLED_ERROR(const std::string& msg, int type, int code); 31 | #endif 32 | 33 | namespace promise 34 | { 35 | enum ResolvedState 36 | { 37 | kNotResolved = 0, 38 | kSucceeded = 1, 39 | kFailed = 2 40 | }; 41 | 42 | #define PROMISE_LOG(fmtString,...) printf("promise: " fmtString"\n", ##__VA_ARGS__) 43 | #ifdef PROMISE_DEBUG_REFS 44 | #define PROMISE_LOG_REF(fmtString,...) PROMISE_LOG(fmtString, ##__VA_ARGS__) 45 | #else 46 | #define PROMISE_LOG_REF(fmtString,...) 47 | #endif 48 | 49 | static const char* kNoMoreCallbacksMsg = 50 | "No more space for promise callbacks, please increase the L template argument"; 51 | 52 | //=== 53 | struct _Void{}; 54 | typedef _Void Void; 55 | 56 | template 57 | struct MaskVoid { typedef V type;}; 58 | template<> 59 | struct MaskVoid {typedef _Void type;}; 60 | 61 | //get function/lambda return type, regardless of argument count and types 62 | template 63 | struct FuncTraits: public FuncTraits{}; 64 | 65 | template 66 | struct FuncTraits { typedef R RetType; enum {nargs = sizeof...(Args)};}; 67 | 68 | template 69 | struct FuncTraits { typedef R RetType; enum {nargs = sizeof...(Args)};}; 70 | 71 | template 72 | struct FuncTraits { typedef R RetType; enum {nargs = sizeof...(Args)};}; 73 | //=== 74 | struct IVirtDtor 75 | { virtual ~IVirtDtor() {} }; 76 | 77 | template 78 | class Promise; 79 | 80 | //Promise error class. We need it to be a refcounted object, because 81 | //often the user would return somehting like return PromiseError(...) 82 | //That would be converted to a failed promise object, and the Error would 83 | //need to be copied into the Promise. By having a refcounted dynamic object, 84 | //only the pointer will be transferred and the refcount changed. 85 | struct ErrorShared 86 | { 87 | std::string mMsg; 88 | int mCode; 89 | int mType; 90 | mutable bool mHandled = false; 91 | ErrorShared(const std::string& aMsg, int aCode=0, int aType=0) 92 | :mMsg(aMsg),mCode(aCode),mType(aType){} 93 | ~ErrorShared() 94 | { 95 | if (!mHandled) 96 | PROMISE_ON_UNHANDLED_ERROR(mMsg, mType, mCode); 97 | } 98 | static void defaultOnUnhandledError(const std::string& msg, int type, int code) 99 | { 100 | fprintf(stderr, "WARNING: Unhandled promise fail. Error: '%s', type: %d, code: %d\n", msg.c_str(), type, code); 101 | } 102 | }; 103 | 104 | enum 105 | { 106 | kErrorTypeGeneric = 1 107 | }; 108 | enum 109 | { 110 | kErrException = 1, 111 | kErrAbort = 2, 112 | kErrTimeout = 3 113 | }; 114 | 115 | class Error: protected std::shared_ptr 116 | { 117 | protected: 118 | Error(): Base(nullptr){} 119 | public: 120 | typedef std::shared_ptr Base; 121 | Error(const std::string& msg, int code=0, int type=kErrorTypeGeneric) 122 | :Base(std::make_shared(msg, code, type)) 123 | {} 124 | Error(const char* msg, int code=0, int type=kErrorTypeGeneric) 125 | :Base(std::make_shared(msg?msg:"", code, type)) 126 | {} 127 | using Base::operator=; 128 | const std::string& msg() const {return get()->mMsg;} 129 | const char* what() const {return get()->mMsg.c_str();} 130 | int type() const {return get()->mType;} 131 | int code() const {return get()->mCode;} 132 | void setHandled() const { get()->mHandled = true; } 133 | bool handled() const { return get()->mHandled; } 134 | std::string toString() const 135 | { 136 | return "Error: '"+get()->mMsg+"'\nType: "+ 137 | std::to_string(get()->mType)+" Code: "+std::to_string(get()->mCode); 138 | } 139 | template 140 | friend class Promise; 141 | }; 142 | 143 | class PromiseBase 144 | { 145 | protected: 146 | public: 147 | virtual PromiseBase* clone() const = 0; 148 | virtual ~PromiseBase(){} 149 | }; 150 | 151 | template 152 | class CallbackList 153 | { 154 | protected: 155 | C* items[L]; 156 | int mCount; 157 | public: 158 | CallbackList():mCount(0){} 159 | inline void checkCanAdd() const 160 | { 161 | if (mCount>=L) 162 | throw std::runtime_error(kNoMoreCallbacksMsg); 163 | } 164 | /** 165 | * Takes ownership of callback, copies the promise. 166 | * Accepts the callback as a smart pointer of type SP. 167 | * This is because the method can throw if the list is full, 168 | * and in this case the smartpointer will prevent the pointer leak. 169 | */ 170 | template 171 | inline void push(SP& cb) 172 | { 173 | if (mCount >= L) 174 | throw std::runtime_error(kNoMoreCallbacksMsg); 175 | items[mCount++] = cb.release(); 176 | } 177 | 178 | inline C*& operator[](int idx) 179 | { 180 | assert((idx >= 0) && (idx <= mCount)); 181 | return items[idx]; 182 | } 183 | inline const C*& operator[](int idx) const 184 | { 185 | assert((idx >= 0) && (idx <= mCount)); 186 | return items[idx]; 187 | } 188 | inline C*& first() 189 | { 190 | assert(mCount > 0); 191 | return items[0]; 192 | } 193 | inline int count() const {return mCount;} 194 | inline void addListMoveItems(CallbackList& other) 195 | { 196 | int cnt = other.count(); 197 | if (mCount+cnt > L) 198 | throw std::runtime_error(kNoMoreCallbacksMsg); 199 | for (int i=0; i::value, "Callback type must be inherited from IVirtDtor"); 206 | for (int i=0; i 217 | class Promise: public PromiseBase 218 | { 219 | public: 220 | protected: 221 | template 222 | struct ICallback: public IVirtDtor 223 | { 224 | virtual void operator()(const P&) = 0; 225 | virtual void rejectNextPromise(const Error&) = 0; 226 | }; 227 | 228 | template 229 | struct ICallbackWithPromise: public ICallback

230 | { 231 | public: 232 | Promise nextPromise; 233 | virtual void rejectNextPromise(const Error& err) { nextPromise.reject(err); } 234 | ICallbackWithPromise(const Promise& next): nextPromise(next){} 235 | }; 236 | 237 | template 238 | struct Callback: public ICallbackWithPromise 239 | { 240 | protected: 241 | CB mCb; 242 | public: 243 | virtual void operator()(const P& arg) { mCb(arg, *this); } 244 | Callback(CB&& cb, const Promise& next) 245 | :ICallbackWithPromise(next), mCb(std::forward(cb)){} 246 | CB& callback() { return mCb; } 247 | }; 248 | typedef ICallback::type> ISuccessCb; 249 | typedef ICallback IFailCb; 250 | typedef ICallbackWithPromise IFailCbWithPromise; 251 | 252 | template 253 | struct SuccessCb: public Callback::type, CB, TP> 254 | { 255 | SuccessCb(CB&& cb, const Promise& next) //can't use ctotr inharitance because MSVC 2013 does not support it 256 | :Callback::type, CB, TP>(std::forward(cb), next){} 257 | }; 258 | 259 | template 260 | struct FailCb: public Callback 261 | { 262 | FailCb(CB&& cb, const Promise& next) 263 | :Callback(std::forward(cb), next){} 264 | }; 265 | /** Helper funtion to be able to deduce the callback type of the passed lambda and create and 266 | * Callback object with that type. We cannot do that by directly calling the Callback constructor 267 | */ 268 | template 269 | ICallback::type>* createCb(CB&& cb, Promise& next) 270 | { 271 | return new Callback::type, CB, TP>(std::forward(cb), next); 272 | } 273 | //=== 274 | struct SharedObj 275 | { 276 | struct CbLists 277 | { 278 | CallbackList mSuccessCbs; 279 | CallbackList mFailCbs; 280 | }; 281 | int mRefCount; 282 | CbLists* mCbs; 283 | ResolvedState mResolved; 284 | bool mPending; 285 | Promise mMaster; 286 | typename MaskVoid::type>::type mResult; 287 | Error mError; 288 | SharedObj() 289 | :mRefCount(1), mCbs(NULL), mResolved(kNotResolved), 290 | mPending(false), mMaster(_Empty()) 291 | { 292 | PROMISE_LOG_REF("%p: addRef -> 1 (SharedObj ctor)", this); 293 | } 294 | void ref() 295 | { 296 | mRefCount++; 297 | PROMISE_LOG_REF("%p: ref -> %d", this, mRefCount); 298 | } 299 | void unref() 300 | { 301 | if (--mRefCount > 0) 302 | { 303 | PROMISE_LOG_REF("%p: unref -> %d", this, mRefCount); 304 | return; 305 | } 306 | assert(mRefCount == 0); 307 | PROMISE_LOG_REF("%p: unref -> 0 (deleting SharedObj)", this); 308 | delete this; 309 | } 310 | ~SharedObj() 311 | { 312 | if (mCbs) 313 | { 314 | mCbs->mSuccessCbs.clear(); 315 | mCbs->mFailCbs.clear(); 316 | delete mCbs; 317 | } 318 | } 319 | inline CbLists& cbs() 320 | { 321 | if (!mCbs) 322 | mCbs = new CbLists; 323 | return *mCbs; 324 | } 325 | }; 326 | 327 | template 328 | struct RemovePromise 329 | { typedef typename std::remove_const::type Type; }; 330 | template 331 | struct RemovePromise > 332 | { typedef typename std::remove_const::type Type; }; 333 | 334 | //=== 335 | struct CallCbHandleVoids 336 | { 337 | template::value && !std::is_same::value, int>::type> 338 | static Promise call(CB& cb, const In& val) { return cb(val); } 339 | 340 | template::value && !std::is_same::value, int>::type> 341 | static Promise call(CB& cb, const _Void& val) { return cb(); } 342 | 343 | template::value && std::is_same::value, int>::type> 344 | static Promise call(CB& cb, const In& val){ cb(val); return _Void(); } 345 | 346 | template::value && std::is_same::value, int>::type> 347 | static Promise call(CB& cb, const _Void& val) { cb(); return _Void(); } 348 | }; 349 | //=== 350 | void reset(SharedObj* other=NULL) 351 | { 352 | if (mSharedObj) 353 | { 354 | mSharedObj->unref(); 355 | } 356 | mSharedObj = other; 357 | if (mSharedObj) 358 | { 359 | mSharedObj->ref(); 360 | } 361 | } 362 | inline CallbackList& thenCbs() {return mSharedObj->cbs().mSuccessCbs;} 363 | inline CallbackList& failCbs() {return mSharedObj->cbs().mFailCbs;} 364 | SharedObj* mSharedObj; 365 | template friend class Promise; 366 | public: 367 | typedef T Type; 368 | /** @brief Creates an uninitialized promise. 369 | * @attention Use with care - only when subsequent re-assigning 370 | * is guaranteed. 371 | */ 372 | Promise(_Empty): mSharedObj(NULL){} 373 | Promise(): mSharedObj(new SharedObj){} 374 | Promise(const Promise& other): mSharedObj(other.mSharedObj) 375 | { 376 | if (mSharedObj) 377 | mSharedObj->ref(); 378 | } 379 | template ::value, int>::type> 380 | Promise(const typename MaskVoid::type& val): mSharedObj(new SharedObj) 381 | { 382 | resolve(val); 383 | } 384 | Promise(typename MaskVoid::type&& val): mSharedObj(new SharedObj) 385 | { 386 | resolve(std::forward::type>(val)); 387 | } 388 | 389 | Promise(const Error& err): mSharedObj(new SharedObj) 390 | { 391 | assert(err); 392 | reject(err); 393 | } 394 | Promise& operator=(const Promise& other) 395 | { 396 | reset(other.mSharedObj); 397 | return *this; 398 | } 399 | 400 | virtual ~Promise() 401 | { 402 | if (mSharedObj) 403 | { 404 | mSharedObj->unref(); 405 | } 406 | } 407 | int done() const 408 | { 409 | if (!mSharedObj) 410 | return kNotResolved; 411 | return getMaster().mSharedObj->mResolved; 412 | } 413 | bool succeeded() const { return done() == kSucceeded; } 414 | bool failed() const { return done() == kFailed; } 415 | const Error& error() const 416 | { 417 | assert(mSharedObj); 418 | assert(done() == kFailed); 419 | auto& master = mSharedObj->mMaster; 420 | return master.mSharedObj 421 | ? master.mSharedObj->mError 422 | : mSharedObj->mError; 423 | } 424 | template 425 | const typename std::enable_if::value, Ret>::type& value() const 426 | { 427 | assert(mSharedObj); 428 | auto master = mSharedObj->mMaster; 429 | if (master.mSharedObj) 430 | { 431 | assert(master.done()); 432 | return master.mSharedObj->mResult; 433 | } 434 | else 435 | { 436 | assert(done() == kSucceeded); 437 | return mSharedObj->mResult; 438 | } 439 | } 440 | protected: 441 | virtual PromiseBase* clone() const 442 | { return new Promise(*this); } 443 | bool hasMaster() const { return (mSharedObj->mMaster.mSharedObj != nullptr) ; } 444 | //The master of a promise is the actual promise that gets resolved, 445 | //similar to the 'deferred' object in some promise libraries. 446 | //It contains the callbacks and the state. Promises that have a 447 | //master just forward the attached callbacks to the master. These promises 448 | //are generated during the chaining process - all of them attach to the 449 | //initial, master promise. 450 | const Promise& getMaster() const 451 | { 452 | assert(mSharedObj); 453 | auto& master = mSharedObj->mMaster; 454 | auto& ret = master.mSharedObj ? master : *this; 455 | assert(!ret.hasMaster()); 456 | return ret; 457 | } 458 | //non-const version of the method 459 | Promise& getMaster() 460 | { 461 | assert(mSharedObj); 462 | auto& master = mSharedObj->mMaster; 463 | auto& ret = master.mSharedObj ? master : *this; 464 | assert(!ret.hasMaster()); 465 | return ret; 466 | } 467 | 468 | /** Creates a wrapper function around a then() or fail() handler that handles exceptions and propagates 469 | * the result to resolve/reject chained promises. \c In is the type of the callback's parameter, 470 | * \c Out is its return type, \c CB is the type of the callback itself. 471 | */ 472 | template 473 | ICallback* createChainedCb(CB&& cb, Promise& next) 474 | { 475 | //cb must have the singature Promise(const In&) 476 | return createCb( 477 | [cb](const In& result, ICallbackWithPromise::type, Out >& handler) 478 | mutable->void 479 | { 480 | Promise& next = handler.nextPromise; //the 'chaining' promise 481 | Promise promise((_Empty())); //the promise returned by the user callback 482 | try 483 | { 484 | promise = CallCbHandleVoids::template call(cb, result); 485 | } 486 | catch(std::exception& e) 487 | { 488 | next.reject(Error(e.what(), kErrException)); 489 | return; 490 | } 491 | catch(Error& e) 492 | { 493 | next.reject(e); 494 | return; 495 | } 496 | catch(const char* e) 497 | { 498 | next.reject(Error(e, kErrException)); 499 | return; 500 | } 501 | catch(...) 502 | { 503 | next.reject(Error("(unknown exception type)", kErrException)); 504 | return; 505 | } 506 | 507 | // connect the promise returned by the user's callback (actually its master) 508 | // to the chaining promise, returned earlier by then() or fail() 509 | Promise& master = promise.getMaster(); //master is the promise that actually gets resolved, equivalent to the 'deferred' object 510 | assert(!next.hasMaster()); 511 | next.mSharedObj->mMaster = master; //makes 'next' attach subsequently added callbacks to 'master' 512 | assert(next.hasMaster()); 513 | // Move the callbacks and errbacks of 'next' to 'master' 514 | if (!master.hasCallbacks()) 515 | { 516 | master.mSharedObj->mCbs = next.mSharedObj->mCbs; 517 | next.mSharedObj->mCbs = nullptr; 518 | } 519 | else 520 | { 521 | auto& nextCbs = next.thenCbs(); 522 | if (nextCbs.count()) 523 | master.thenCbs().addListMoveItems(nextCbs); 524 | 525 | auto& nextEbs = next.failCbs(); 526 | if (nextEbs.count()) 527 | master.failCbs().addListMoveItems(nextEbs); 528 | } 529 | //==== 530 | if (master.mSharedObj->mPending) 531 | master.doPendingResolveOrFail(); 532 | }, next); 533 | } 534 | 535 | public: 536 | /** 537 | * The Out template argument is the return type of the provided callback \c cb 538 | * It is the same as the argument type of the next chained then() 539 | * callback(if any). The \c cb callback can return a value of type \c Out or a 540 | * \c Promise instance 541 | */ 542 | template 543 | auto then(F&& cb)->Promise::RetType>::Type > 544 | { 545 | if (mSharedObj->mMaster.mSharedObj) //if we are a slave promise (returned by then() or fail()), forward callbacks to our master promise 546 | return mSharedObj->mMaster.then(std::forward(cb)); 547 | 548 | if (mSharedObj->mResolved == kFailed) 549 | return mSharedObj->mError; 550 | 551 | typedef typename RemovePromise::RetType>::Type Out; 552 | Promise next; 553 | 554 | std::unique_ptr resolveCb(createChainedCb::type, Out, 555 | typename FuncTraits::RetType>(std::forward(cb), next)); 556 | 557 | if (mSharedObj->mResolved == kSucceeded) 558 | { 559 | (*resolveCb)(mSharedObj->mResult); 560 | } 561 | else 562 | { 563 | assert((mSharedObj->mResolved == kNotResolved)); 564 | thenCbs().push(resolveCb); 565 | } 566 | 567 | return next; 568 | } 569 | /** Adds a handler to be executed in case the promise is rejected 570 | * \note 571 | * fail() must always return a promise of the same type as the one of the 572 | * promise on which it is called (i.e. type T) 573 | * This is because the next promise in the chain can have a then() handler, 574 | * which in case of no success, will get its value from the promise before 575 | * the fail(). In other words 576 | * fail()-s in the chain must always preserve the result type from the last 577 | * then() 578 | */ 579 | template 580 | auto fail(F&& eb)->Promise 581 | { 582 | auto& master = mSharedObj->mMaster; 583 | if (master.mSharedObj) //if we are a slave promise (returned by then() or fail()), forward callbacks to our master promise 584 | return master.fail(std::forward(eb)); 585 | 586 | if (mSharedObj->mResolved == kSucceeded) 587 | return mSharedObj->mResult; //don't call the errorback, just return the successful resolve value 588 | 589 | Promise next; 590 | std::unique_ptr failCb(createChainedCb::RetType>(std::forward(eb), next)); 592 | 593 | if (mSharedObj->mResolved == kFailed) 594 | { 595 | (*failCb)(mSharedObj->mError); 596 | mSharedObj->mError.setHandled(); 597 | } 598 | else 599 | { 600 | assert((mSharedObj->mResolved == kNotResolved)); 601 | failCbs().push(failCb); 602 | } 603 | 604 | return next; 605 | } 606 | //val can be a by-value param, const& or && 607 | template 608 | void resolve(V&& val) 609 | { 610 | if (mSharedObj->mResolved) 611 | throw std::runtime_error("Already resolved/rejected"); 612 | 613 | mSharedObj->mResult = std::forward(val); 614 | mSharedObj->mResolved = kSucceeded; 615 | 616 | if (hasCallbacks()) 617 | doResolve(mSharedObj->mResult); 618 | else 619 | mSharedObj->mPending = true; 620 | } 621 | template ::value, int>::type> 622 | void resolve() 623 | { 624 | resolve(_Void()); 625 | } 626 | 627 | protected: 628 | inline bool hasCallbacks() const { return (mSharedObj->mCbs!=NULL); } 629 | void doResolve(const typename MaskVoid::type& val) 630 | { 631 | auto& cbs = thenCbs(); 632 | int cnt = cbs.count(); 633 | if (cnt) 634 | { 635 | if (cnt == 1) //optimize for single callback 636 | { 637 | (*cbs.first())(val); 638 | } 639 | else 640 | { 641 | for (int i=0; i(ebs.first())->nextPromise.resolve(val); 658 | } 659 | else 660 | { 661 | for (int i=0; i(item)->nextPromise.resolve(val); 665 | } 666 | } 667 | } 668 | } 669 | public: 670 | void reject(const Error& err) 671 | { 672 | assert(err); 673 | if (mSharedObj->mResolved) 674 | throw std::runtime_error("Alrady resolved/rejected"); 675 | 676 | mSharedObj->mError = err; 677 | mSharedObj->mResolved = kFailed; 678 | 679 | if (hasCallbacks()) 680 | doReject(err); 681 | else 682 | mSharedObj->mPending = true; 683 | } 684 | inline void reject(const std::string& msg) 685 | { 686 | reject(Error(msg)); 687 | } 688 | inline void reject(const char* msg) 689 | { 690 | if (!msg) 691 | msg = ""; 692 | reject(Error(msg)); 693 | } 694 | inline void reject(int code, int type) 695 | { 696 | reject(Error("", code, type)); 697 | } 698 | inline void reject(const std::string& msg, int code, int type) 699 | { 700 | reject(Error(msg, code, type)); 701 | } 702 | 703 | protected: 704 | void doReject(const Error& err) 705 | { 706 | assert(mSharedObj->mError); 707 | assert(mSharedObj->mResolved == kFailed); 708 | auto& ebs = failCbs(); 709 | int cnt = ebs.count(); 710 | if (cnt) 711 | { 712 | (*static_cast(ebs.first()))(err); 713 | err.setHandled(); 714 | for (int i=1; i(ebs[i]))(err); 716 | } 717 | //propagate past success handlers till a fail handler is found 718 | auto& cbs = thenCbs(); 719 | cnt = cbs.count(); 720 | if (cnt) 721 | { 722 | cbs.first()->rejectNextPromise(err); 723 | for (int i=1; irejectNextPromise(err); 729 | } 730 | } 731 | } 732 | void doPendingResolveOrFail() 733 | { 734 | if (!hasCallbacks()) 735 | return; 736 | assert(mSharedObj->mPending); 737 | auto state = mSharedObj->mResolved; 738 | assert(state != kNotResolved); 739 | if (state == kSucceeded) 740 | doResolve(mSharedObj->mResult); 741 | else 742 | { 743 | assert(state == kFailed); 744 | doReject(mSharedObj->mError); 745 | } 746 | } 747 | }; 748 | 749 | template 750 | inline Promise reject(const Error& err) 751 | { 752 | return Promise(err); 753 | } 754 | 755 | struct WhenStateShared 756 | { 757 | int numready = 0; 758 | Promise output; 759 | bool addLast = false; 760 | int totalCount = 0; 761 | }; 762 | 763 | struct WhenState: public std::shared_ptr 764 | { 765 | WhenState():std::shared_ptr(new WhenStateShared){} 766 | }; 767 | 768 | template ::value, int>::type> 769 | inline void _when_add_single(WhenState& state, Promise& pms) 770 | { 771 | state->totalCount++; 772 | pms.then([state](const T& ret) 773 | { 774 | int n = ++(state->numready); 775 | PROMISE_LOG_REF("when: %p: numready = %d", state.get(), state->numready); 776 | if (state->addLast && (n >= state->totalCount)) 777 | { 778 | assert(n == state->totalCount); 779 | if (!state->output.done()) 780 | state->output.resolve(); 781 | } 782 | return ret; 783 | }); 784 | pms.fail([state](const Error& err) 785 | { 786 | if (!state->output.done()) 787 | state->output.reject(err); 788 | return err; 789 | }); 790 | } 791 | 792 | template ::value, int>::type> 793 | inline void _when_add_single(WhenState& state, Promise& pms) 794 | { 795 | state->totalCount++; 796 | pms.then([state]() 797 | { 798 | int n = ++(state->numready); 799 | PROMISE_LOG_REF("when: %p: numready = %d", state.get(), state->numready); 800 | if (state->addLast && (n >= state->totalCount)) 801 | { 802 | assert(n == state->totalCount); 803 | if (!state->output.done()) 804 | state->output.resolve(); 805 | } 806 | }); 807 | pms.fail([state](const Error& err) 808 | { 809 | if (!state->output.done()) 810 | state->output.reject(err); 811 | return err; 812 | }); 813 | } 814 | 815 | template 816 | inline void _when_add(WhenState& state, Promise& promise) 817 | { 818 | //this is called when the final promise is added. Now we know the actual count 819 | state->addLast = true; 820 | _when_add_single(state, promise); 821 | } 822 | template 823 | inline void _when_add(WhenState& state, Promise& promise, 824 | Args... promises) 825 | { 826 | _when_add_single(state, promise); 827 | _when_add(state, promises...); 828 | } 829 | 830 | template 831 | inline Promise when(Args... inputs) 832 | { 833 | WhenState state; 834 | _when_add(state, inputs...); 835 | return state->output; 836 | } 837 | 838 | template 839 | inline Promise when(std::vector>& promises) 840 | { 841 | if (promises.empty()) 842 | return Void(); 843 | 844 | WhenState state; 845 | size_t countMinus1 = promises.size()-1; 846 | for (size_t i=0; i < countMinus1; i++) 847 | { 848 | _when_add_single

(state, promises[i]); 849 | } 850 | state->addLast = true; 851 | _when_add_single

(state, promises[countMinus1]); 852 | return state->output; 853 | } 854 | 855 | }//end namespace promise 856 | #endif 857 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | file(GLOB TEST_HEADERS "async-test/include/*") 4 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 5 | set(CMAKE_CXX_FLAGS "-fsanitize=address -fsanitize=leak") 6 | set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=address -fsanitize=leak") 7 | add_definitions(-std=c++11) 8 | endif() 9 | include_directories(.. async-test/include) 10 | set(CMAKE_BUILD_TYPE Debug) 11 | 12 | add_executable(promise-test promise-test.cpp ../promise.hpp ${TEST_HEADERS}) 13 | 14 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | promise-test: ../promise.hpp async-test/include/* 2 | g++ -std=c++11 -O0 -g -fsanitize=address -I.. -Iasync-test/include promise-test.cpp -o ./promise-test 3 | all: promise-test 4 | clean: 5 | rm -f ./promise-test 6 | run: promise-test 7 | ./promise-test 8 | 9 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests for the promise library 2 | 3 | This directory contains the test suite for the promise.hpp library. The tests use the `async-test` unit test framework, 4 | which should already be checked as a submodule in this directory. 5 | 6 | There are two ways to build the test suite "out of the box" - using the provided GNU `Makefile`, or using the provided `CMakeLists.txt` file. 7 | 8 | ## Using the Makefile 9 | This option supports only `gcc` and `clang` compilers and requires GNU `make`. 10 | Open a console in the `tests` subdir and type `make run`. The test suite should build and run. 11 | 12 | ## Using the CMakeLists.txt file 13 | This option is compiler-agnostic and requires `cmake` 14 | Open a terminal in `tests`, create a `build` subdir, and then run `cmake ..`, optionally selecting the type of project you want with the `-G ` option, i.e. `cmake -G "Visual Studio 15 2017" ..` 15 | 16 | ## Manual build 17 | You can easily build the test suite manually. Simply compile `promise-test.cpp`, 18 | providing include path to the directories `./asyc-test` and `..`. Note that C++11 support must be enabled in the compiler. 19 | -------------------------------------------------------------------------------- /tests/promise-test.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @author Alexander Vassilev 3 | @copyright MEGA Limited, 2014 4 | @license BSD 2-Clause "Simplified" License 5 | */ 6 | 7 | //#define TESTLOOP_LOG_DONES 8 | //#define TESTLOOP_DEBUG 9 | //#define TESTLOOP_DEFAULT_DONE_TIMEOUT 4000 10 | 11 | #include 12 | #define PROMISE_ON_UNHANDLED_ERROR testUnhandledError 13 | #include 14 | 15 | TESTS_INIT(); 16 | using namespace promise; 17 | 18 | std::function gUnhandledHandler = 19 | [](const std::string& msg, int type, int code) 20 | { 21 | printf("WARNING: Unhandled promise fail. Error: %s, type: %d, code: %d\n", msg.c_str(), type, code); 22 | }; 23 | 24 | void testUnhandledError(const std::string& msg, int type, int code) 25 | { 26 | gUnhandledHandler(msg, type, code); 27 | } 28 | 29 | int main() 30 | { 31 | 32 | TestGroup("General") 33 | { 34 | asyncTest("Promise: resolve->then(ret error)->fail(recover)->then", 35 | {{"fail", "order", 2}, {"then1", "order", 1}, {"then2", "order", 3}}) 36 | { 37 | Promise pms; 38 | pms 39 | .then([&](int x)->Promise 40 | { 41 | doneOrError(x == 12345678, "then1"); 42 | Promise ret; 43 | loop.schedCall([ret]() mutable 44 | { ret.reject("test error message"); }, 100); 45 | return ret; 46 | }) 47 | .fail([&](const Error& err) mutable 48 | { 49 | doneOrError(err.msg() == "test error message", "fail"); 50 | Promise ret; 51 | loop.schedCall([ret]() mutable 52 | { ret.resolve(87654321); }, 100); 53 | return ret; 54 | }) 55 | .then([&](int x) mutable 56 | { 57 | doneOrError(x == 87654321, "then2"); 58 | }); 59 | 60 | loop.schedCall([pms]() mutable 61 | { pms.resolve(12345678); }, 100); 62 | }); 63 | 64 | asyncTest("Promise: resolve->then(ret error)->fail(recover)->then", 65 | {{"fail", "order", 2}, {"then1", "order", 1}, {"then2", "order", 3}}) 66 | { 67 | Promise pms; 68 | pms 69 | .then([&]() 70 | { 71 | loop.done("then1"); 72 | Promise ret; 73 | loop.schedCall([ret]() mutable 74 | { ret.reject("test error message"); }, 100); 75 | return ret; 76 | }) 77 | .fail([&](const Error& err) mutable 78 | { 79 | doneOrError(err.msg() == "test error message", "fail"); 80 | Promise ret; 81 | loop.schedCall([ret]() mutable 82 | { ret.resolve(); }, 100); 83 | return ret; 84 | }) 85 | .then([&]() mutable 86 | { 87 | loop.done("then2"); 88 | }); 89 | 90 | loop.schedCall([pms]() mutable 91 | { pms.resolve(); }, 100); 92 | }); 93 | asyncTest("Rejected should skip all then()-s", {"fail1"}) 94 | { 95 | Promise pms; 96 | pms.then([&]() 97 | { 98 | test.error("then() must not be called by a rejected promise"); 99 | }) 100 | .then([&]() 101 | { 102 | test.error("second then() must not be called by a rejected promise"); 103 | }) 104 | .fail([&](const Error& err) 105 | { 106 | doneOrError(err.msg() == "test message", "fail1"); 107 | }); 108 | pms.reject("test message"); 109 | }); 110 | 111 | asyncTest("Change type", {"then1", "then2", "then-final"}) 112 | { 113 | Promise pms; 114 | pms.then([&](int a) 115 | { 116 | doneOrError(a == 1212, "then1"); 117 | }) 118 | .then([&]() 119 | { 120 | loop.done("then2"); 121 | return 1; 122 | }) 123 | .then([&](int a) 124 | { 125 | doneOrError(a == 1, "then-final"); 126 | }); 127 | pms.resolve(1212); 128 | }); 129 | asyncTest("Should default-return resolved Promise", {"then1", "then-final"}) 130 | { 131 | Promise pms; 132 | pms.then([&](int a) mutable 133 | { 134 | doneOrError(a == 1234, "then1"); 135 | }) 136 | .then([&]() mutable 137 | { 138 | test.done("then-final"); 139 | }); 140 | pms.resolve(1234); 141 | }); 142 | asyncTest("Should propagete failure through void callback (r180f82)", {"then"}) 143 | { 144 | Promise pms; 145 | pms.then([&](int a) 146 | { 147 | return Promise(Error("message")); 148 | }) 149 | .then([&]() mutable 150 | { 151 | test.error("then() called by a rejected promise"); 152 | }) 153 | .fail([&](const Error& err) mutable 154 | { 155 | doneOrError(err.msg() == "message", "then"); 156 | }); 157 | loop.schedCall([pms]() mutable { pms.resolve(1); }); 158 | }); 159 | asyncTest("return .then() from then() should propagate (r6f550b)") 160 | { 161 | Promise pms; 162 | pms.then([&](int a) 163 | { 164 | Promise pms1(1); 165 | return pms1 166 | .then([](int a) 167 | { 168 | return a+1; 169 | }); 170 | }) 171 | .then([&](int a) 172 | { 173 | doneOrError(a == 2, ); 174 | }); 175 | pms.resolve(1); 176 | 177 | }); 178 | asyncTest("return .then() from then() from then()\n should propagate (rb1209c)") 179 | { 180 | Promise pms; 181 | pms.then([&](int a) 182 | { 183 | Promise pms1(1); 184 | return pms1.then([&](int a) 185 | { 186 | Promise pms2(1); 187 | return pms2 188 | .then([](int a) 189 | { 190 | return a+1; 191 | }); 192 | }); 193 | }) 194 | .then([&](int a) 195 | { 196 | doneOrError(a == 2, ); 197 | }); 198 | pms.resolve(1); 199 | }); 200 | 201 | asyncTest("Should propagate resolution from nested scope", 202 | {{"inner", "order", 1}, {"then-final", "order", 2}}) 203 | { 204 | Promise pms; 205 | pms.then([&](int a) mutable 206 | { 207 | Promise pms2; 208 | loop.schedCall([pms2]() mutable { pms2.resolve(1234); }, 100); 209 | return pms2 210 | .then([&](int a) 211 | { 212 | Promise pms3; 213 | loop.schedCall([pms3]() mutable { pms3.resolve(1234); }, 100); 214 | return pms3 215 | .then([&](int a) 216 | { 217 | Promise pms4; 218 | loop.schedCall([pms4]() mutable { pms4.resolve(1234); }, 100); 219 | return pms4 220 | .then([&](int a) 221 | { 222 | Promise pms5; 223 | loop.schedCall([pms5]() mutable { pms5.resolve(1234); }, 100); 224 | return pms5 225 | .then([&](int a) 226 | { 227 | Promise pms6; 228 | loop.schedCall([pms6]() mutable { pms6.resolve(1234); }, 100); 229 | return pms6 230 | .then([&](int a) 231 | { 232 | test.done("inner"); 233 | return a+2; 234 | }); 235 | }); 236 | }); 237 | }); 238 | }); 239 | }) 240 | .then([&](int a) mutable 241 | { 242 | doneOrError(a == 1236, "then-final"); 243 | }); 244 | pms.resolve(1); 245 | }); 246 | asyncTest("Should hold the resolved value even if handlers alerady attached") 247 | { 248 | Promise> pms; 249 | pms.then([&](const std::pair& val) 250 | { 251 | //Fix doResolve() being called with already moved value instead of 252 | //mSharedObj->mResult 253 | check(val.first == "test123"); 254 | }); 255 | when(pms) 256 | .then([&]() 257 | { 258 | //Fix resolve() calling doResolve() with the value without saving 259 | //the value mSharedObj->mResult. This worked before we added the value() 260 | //method, as the only way we could access the promise value was in 261 | //the .then() callbacks. 262 | doneOrError(pms.value().first == "test123",); 263 | }); 264 | pms.resolve(std::make_pair("test123", "fubar")); 265 | }); 266 | }); 267 | 268 | TestGroup("Exception tests") 269 | { 270 | asyncTest("Should fail if std::exception in then() block") 271 | { 272 | Promise pms; 273 | pms.then([&](int a) 274 | { 275 | check(a == 1); 276 | throw std::runtime_error("test exception"); 277 | }) 278 | .then([&]() 279 | { 280 | test.error("should not execute then() after a then() with exception"); 281 | return; 282 | }) 283 | .fail([&](const Error& err) 284 | { 285 | doneOrError(err.msg() == "test exception", ); 286 | }); 287 | pms.resolve(1); 288 | }); 289 | asyncTest("Should fail with message if char* exception in then() block", {"fail"}) 290 | { 291 | Promise pms; 292 | pms.then([&](int a) 293 | { 294 | check(a == 1); 295 | throw "test char* exception"; 296 | }) 297 | .then([&]() 298 | { 299 | test.error("should not execute then() after a then() with exception"); 300 | return; 301 | }) 302 | .fail([&](const Error& err) 303 | { 304 | doneOrError(err.msg() == "test char* exception", "fail"); 305 | }); 306 | pms.resolve(1); 307 | }); 308 | asyncTest("Should fail if non-std exception in then() block", {"fail"}) 309 | { 310 | Promise pms; 311 | pms.then([&](int a) 312 | { 313 | check(a == 1); 314 | throw 10; 315 | }) 316 | .then([&]() 317 | { 318 | test.error("should not execute then() after a then() with exception"); 319 | return; 320 | }) 321 | .fail([&](const Error& err) 322 | { 323 | test.done("fail"); 324 | }); 325 | pms.resolve(1); 326 | }); 327 | asyncTest("Should fail if exception in fail() block", {"fail", "fail excep"}) 328 | { 329 | Promise pms; 330 | pms.then([&](int) 331 | { 332 | test.error("Should not execute then() on failed promise"); 333 | }) 334 | .fail([&](const Error& err) 335 | { 336 | doneOrError(err.msg() == "test message", "fail"); 337 | throw std::runtime_error("Test exception"); 338 | }) 339 | .fail([&](const Error& err) 340 | { 341 | doneOrError(err.msg() == "Test exception", "fail excep"); 342 | }); 343 | pms.reject("test message"); 344 | }); 345 | }); 346 | TestGroup("Double resolve tests") 347 | { 348 | asyncTest("Should throw if trying to resolve resolved promise", {"excep"}) 349 | { 350 | Promise pms; 351 | pms.resolve(1); 352 | check(pms.succeeded()); 353 | try 354 | { 355 | pms.resolve(2); 356 | } 357 | catch(...) 358 | { 359 | test.done("excep"); 360 | } 361 | }); 362 | asyncTest("Should throw if trying to resolve failed promise", {"excep"}) 363 | { 364 | Promise pms; 365 | pms.reject("test error"); 366 | check(pms.failed()); 367 | try 368 | { 369 | pms.resolve(2); 370 | } 371 | catch(...) 372 | { 373 | test.done("excep"); 374 | } 375 | }); 376 | asyncTest("Should throw if trying to reject resolved promise", {"excep"}) 377 | { 378 | Promise pms; 379 | pms.resolve(1); 380 | check(pms.succeeded()); 381 | try 382 | { 383 | pms.reject("test"); 384 | } 385 | catch(...) 386 | { 387 | test.done("excep"); 388 | } 389 | }); 390 | asyncTest("Should throw if trying to reject a failed promise", {"excep"}) 391 | { 392 | Promise pms; 393 | pms.reject("test1"); 394 | check(pms.failed()); 395 | try 396 | { 397 | pms.reject("test2"); 398 | } 399 | catch(...) 400 | { 401 | test.done("excep"); 402 | } 403 | }); 404 | 405 | }); 406 | 407 | TestGroup("when() tests") 408 | { 409 | asyncTest("when() with 2 already resolved promises") 410 | { 411 | Promise pms1; 412 | pms1.resolve(1); 413 | Promise pms2; 414 | pms2.resolve(); 415 | when(pms1, pms2) 416 | .then([&]() 417 | { 418 | test.done(); 419 | }); 420 | }); 421 | asyncTest("when() with 1 already-resolved and one async-resolved promise") 422 | { 423 | Promise pms1; 424 | pms1.resolve(1); 425 | Promise pms2; 426 | when(pms1, pms2) 427 | .then([&]() 428 | { 429 | test.done(); 430 | }); 431 | loop.schedCall([pms2]() mutable { pms2.resolve(); }, 100); 432 | }); 433 | asyncTest("when() with 2 async-resolved promises") 434 | { 435 | Promise pms1; 436 | Promise pms2; 437 | when(pms1, pms2) 438 | .then([&]() 439 | { 440 | test.done(); 441 | }); 442 | loop.schedCall([pms1]() mutable { pms1.resolve(1); }, 100); 443 | loop.schedCall([pms2]() mutable { pms2.resolve(); }, 100); 444 | }); 445 | asyncTest("when() with one already-failed and one async-resolved promise") 446 | { 447 | //when() should fail immediately without waiting for the unresolved promise 448 | Promise pms1; 449 | pms1.reject("Test error message"); 450 | Promise pms2; 451 | when(pms1, pms2) 452 | .then([&]() 453 | { 454 | test.error("Rejected output promise must not call then()"); 455 | }) 456 | .fail([&](const Error& err) 457 | { 458 | doneOrError(err.msg() == "Test error message",); 459 | }); 460 | }); 461 | asyncTest("when() with one async-failed and one async-resolved promise", 462 | {{"first", "order", 1}, {"second", "order", 2}, {"output", "order", 3}}) 463 | { 464 | Promise pms1; 465 | Promise pms2; 466 | when(pms1, pms2) 467 | .then([&]() 468 | { 469 | test.error("Rejected output promise must not call then()"); 470 | }) 471 | .fail([&](const Error& err) 472 | { 473 | doneOrError(err.msg() == "Test message", "output"); 474 | }); 475 | loop.schedCall([pms1, &test]() mutable { test.done("first"); pms1.resolve(); }, -100); 476 | loop.schedCall([pms2, &test]() mutable { test.done("second"); pms2.reject("Test message");}, -100); 477 | }); 478 | asyncTest("when() with two already failed promises") 479 | { 480 | Promise pms1(Error("first rejected")); 481 | Promise pms2(Error("second rejected")); 482 | when(pms1, pms2) 483 | .then([&]() 484 | { 485 | test.error("Output should not succeed"); 486 | }) 487 | .fail([&](const Error& err) 488 | { 489 | doneOrError(err.msg() == "first rejected",); 490 | }); 491 | }); 492 | asyncTest("when() with four async-resolved promises",{ 493 | {"first", "order", 1}, {"second", "order", 2}, {"third", "order", 3}, 494 | {"fourth", "order", 4}, {"output", "order", 5}}) 495 | { 496 | Promise pms1; 497 | Promise pms2; 498 | Promise pms3; 499 | Promise pms4; 500 | when(pms1, pms2, pms3, pms4) 501 | .then([&]() 502 | { 503 | test.done("output"); 504 | }) 505 | .fail([&](const Error& err) 506 | { 507 | test.error("Output promise should not fail()"); 508 | }); 509 | loop.schedCall([pms1, &test]() mutable { test.done("first"); pms1.resolve(); }, -100); 510 | loop.schedCall([pms2, &test]() mutable { test.done("second"); pms2.resolve(1);}, -100); 511 | loop.schedCall([pms3, &test]() mutable { test.done("third"); pms3.resolve("test"); }, -100); 512 | loop.schedCall([pms4, &test]() mutable { test.done("fourth"); pms4.resolve("test fourth"); }, -100); 513 | }); 514 | asyncTest("when() with 3 async-resolved promises, and one async-failed",{ 515 | {"first", "order", 1}, {"second", "order", 2}, {"third", "order", 3}, 516 | {"fourth", "order", 4}, {"output", "order", 5}}) 517 | { 518 | Promise pms1; 519 | Promise pms2; 520 | Promise pms3; 521 | Promise pms4; 522 | when(pms1, pms2, pms3, pms4) 523 | .then([&]() 524 | { 525 | test.error("Output promise should fail, not call then()"); 526 | }) 527 | .fail([&](const Error& err) 528 | { 529 | doneOrError(err.msg() == "test fourth fail", "output"); 530 | }); 531 | loop.schedCall([pms1, &test]() mutable { test.done("first"); pms1.resolve(); }, -100); 532 | loop.schedCall([pms2, &test]() mutable { test.done("second"); pms2.resolve(1);}, -100); 533 | loop.schedCall([pms3, &test]() mutable { test.done("third"); pms3.resolve("test"); }, -100); 534 | loop.schedCall([pms4, &test]() mutable { test.done("fourth"); pms4.reject("test fourth fail"); }, -100); 535 | }); 536 | asyncTest("when() with array of promises, one failed", {"output"}) 537 | { 538 | std::vector> inputs(4); 539 | when(inputs) 540 | .then([&]() 541 | { 542 | test.error(("Output promise should fail, not call then()")); 543 | }) 544 | .fail([&](const Error& err) 545 | { 546 | doneOrError(err.msg() == "test fourth fail", "output"); 547 | }); 548 | 549 | auto& in0 = inputs[0]; 550 | auto& in1 = inputs[1]; 551 | auto& in2 = inputs[2]; 552 | auto& in3 = inputs[3]; 553 | loop.schedCall([in0]() mutable { in0.resolve(10); }, -100); 554 | loop.schedCall([in1]() mutable { in1.resolve(20); }, -100); 555 | loop.schedCall([in2]() mutable { in2.resolve(30); }, -100); 556 | loop.schedCall([in3]() mutable { in3.reject("test fourth fail"); }, -100); 557 | }); 558 | }); 559 | TestGroup("Unhandled promise fail") 560 | { 561 | asyncTest("Unhandled fail with async-rejected promise", {"unhandled"}) 562 | { 563 | Promise pms; 564 | pms.then([&test](int a) 565 | { 566 | test.error("Promise is rejected, should not reslove"); 567 | }); 568 | 569 | gUnhandledHandler = [&loop, &test](const std::string& msg, int type, int code) 570 | { 571 | doneOrError(msg == "test unhandled reported" && type == 1 && code == 2, "unhandled"); 572 | }; 573 | loop.schedCall([pms]() mutable { pms.reject("test unhandled reported", 2, 1);}, -100); 574 | }); 575 | asyncTest("Unhandled fail with alredy rejected promise", {"unhandled"}) 576 | { 577 | Promise pms(Error("test unhandled reported", 2, 1)); 578 | pms.then([&test](int a) 579 | { 580 | test.error("Promise is rejected, should not reslove"); 581 | }); 582 | 583 | gUnhandledHandler = [&loop, &test](const std::string& msg, int type, int code) 584 | { 585 | doneOrError(msg == "test unhandled reported" && type == 1 && code == 2, "unhandled"); 586 | }; 587 | }); 588 | asyncTest("Unhandled fail should not be reported when errors are handled (already failed promise)", {"handled"}) 589 | { 590 | Promise pms(Error("test", 2, 1)); 591 | pms.then([&test](int a) 592 | { 593 | test.error("Promise is rejected, should not reslove"); 594 | }) 595 | .fail([&test, &loop](const Error& err) 596 | { 597 | loop.schedCall([&test]() { test.done("handled"); }, -100); 598 | }); 599 | 600 | gUnhandledHandler = [&loop, &test](const std::string& msg, int type, int code) 601 | { 602 | test.error("unhandled error reported"); 603 | }; 604 | }); 605 | asyncTest("Unhandled fail should not be reported when errors are handled (async failed promise)", {"handled"}) 606 | { 607 | Promise pms; 608 | pms.then([&test](int a) 609 | { 610 | test.error("Promise is rejected, should not reslove"); 611 | }) 612 | .fail([&test, &loop](const Error& err) 613 | { 614 | loop.schedCall([&test]() { test.done("handled"); }, -100); 615 | }); 616 | 617 | gUnhandledHandler = [&loop, &test](const std::string& msg, int type, int code) 618 | { 619 | test.error("unhandled error reported"); 620 | }; 621 | loop.schedCall([pms]() mutable { pms.reject("test"); }); 622 | }); 623 | }); 624 | 625 | return test::gNumFailed; 626 | } 627 | --------------------------------------------------------------------------------