1 module upromised.promise; 2 3 import std.format : format; 4 5 interface Promise_ { 6 final protected void fatal() nothrow { 7 import std.algorithm : each; 8 import std.stdio : stderr; 9 import upromised.backtrace : backtrace; 10 11 try { 12 stderr.writeln("Fatal error"); 13 backtrace.each!(x => stderr.writeln(x)); 14 } catch(Exception) { 15 } 16 } 17 } 18 19 void fatal(Exception e = null, string file = __FILE__, ulong line = __LINE__) nothrow { 20 import core.stdc.stdlib : abort; 21 import std.stdio : stderr; 22 try { 23 stderr.writeln("%s(%s): Fatal error".format(file, line)); 24 if (e) { 25 stderr.writeln(e); 26 } 27 } catch(Exception) { 28 abort(); 29 } 30 abort(); 31 } 32 33 template Promisify(T) { 34 static if (is(T : Promise_)) { 35 alias Promisify = T; 36 } else { 37 alias Promisify = Promise!T; 38 } 39 } 40 static assert(is(Promisify!int == Promise!int)); 41 static assert(is(Promisify!(Promise!int) == Promise!int)); 42 static assert(is(Promisify!void == Promise!void)); 43 44 auto promisify(T)(T a) nothrow { 45 static if (is(T : Promise_)) { 46 return a; 47 } else { 48 return Promise!T.resolved(a); 49 } 50 } 51 auto promisifyCall(T, U...)(T a, U b) nothrow { 52 static if (is(typeof(a(b)) == void)) { 53 try { 54 a(b); 55 return Promise!void.resolved(); 56 } catch(Exception e) { 57 return Promise!void.rejected(e); 58 } 59 } else { 60 try { 61 return promisify(a(b)); 62 } catch(Exception e) { 63 return typeof(promisify(a(b))).rejected(e); 64 } 65 } 66 } 67 68 interface Promise(T_) : Promise_ { 69 import std.typecons : Tuple; 70 71 alias T = T_; 72 73 static if (is(T == void)) { 74 alias Types = Tuple!(); 75 template Then(U) { 76 alias Then = U delegate(); 77 } 78 } else { 79 alias Types = Tuple!(T); 80 template Then(U) { 81 alias Then = U delegate(T); 82 } 83 } 84 85 struct Value { 86 this(Exception e) { 87 this.e = e; 88 } 89 90 static if (!is(T == void)) { 91 this(Exception e, T value) { 92 this.e = e; 93 this.value[0] = value; 94 } 95 } 96 97 Exception e; 98 Types value; 99 } 100 101 static if (is(T == void)) { 102 Promisify!U then(U)(U delegate() cb) nothrow { 103 return then2!U(cb); 104 } 105 } else { 106 Promisify!U then(U)(U delegate(T) cb) nothrow { 107 return then2!U(cb); 108 } 109 110 Promisify!U then(U)(U delegate() cb) nothrow { 111 return then2!U((_) => cb()); 112 } 113 } 114 115 protected Promisify!U then2(U)(Then!U cb) nothrow { 116 auto r = new DelegatePromise!(Promisify!U.T); 117 thenWithTrace((value) nothrow { 118 if (value.e !is null) { 119 r.reject(value.e); 120 } else { 121 scope(failure) r.fatal(); 122 promisifyCall(cb, value.value.expand).thenWithTrace((v) nothrow { 123 r.resolve(v); 124 }); 125 } 126 }); 127 return r; 128 } 129 130 Promise!void except(E,U)(U delegate(E e) cb) nothrow 131 if (is(Promisify!U.T == void) && is (E : Exception)) 132 { 133 auto r = new DelegatePromise!void; 134 thenWithTrace((value) nothrow { 135 scope(failure) r.fatal(); 136 if (value.e !is null) { 137 E e = cast(E)value.e; 138 if (e !is null) { 139 promisifyCall(cb, e).thenWithTrace((value) nothrow { 140 r.resolve(value); 141 }); 142 } else { 143 r.reject(value.e); 144 } 145 } else { 146 r.resolve(); 147 } 148 }); 149 return r; 150 } 151 152 auto finall(U2)(U2 delegate() cb) nothrow { 153 static if (is(Promisify!U2.T == void)) { 154 alias U = T; 155 } else { 156 alias U = U2; 157 } 158 auto r = new DelegatePromise!(Promisify!U.T); 159 thenWithTrace((value) nothrow { 160 scope(failure) r.fatal(); 161 promisifyCall(cb).thenWithTrace((value2) nothrow { 162 Promisify!U.Value value3; 163 value3.e = value2.e is null ? value.e : value2.e; 164 static if (is(Promisify!U2.T == void)) { 165 static if (!is(Promisify!U.T == void)) { 166 value3.value = value.value; 167 } 168 } else { 169 value3.value = value2.value; 170 } 171 172 r.resolve(value3); 173 }); 174 }); 175 return r; 176 } 177 178 Promise!T failure(U)(U delegate(Exception) cb) nothrow 179 if (is(Promisify!U.T == void)) 180 { 181 auto r = new DelegatePromise!T; 182 thenWithTrace((value) nothrow { 183 if (value.e !is null) { 184 promisifyCall(cb, value.e).thenWithTrace((value2) nothrow { 185 r.resolve(value); 186 }); 187 } else { 188 r.resolve(value); 189 } 190 }); 191 return r; 192 } 193 194 static if (is(T == void)) { 195 static Promise!T resolved() nothrow { 196 return resolved_(Value(null)); 197 } 198 } else { 199 static Promise!T resolved(T t) nothrow { 200 return resolved_(Value(null, t)); 201 } 202 } 203 protected static Promise!T resolved_(Value value) { 204 return new class Promise!T { 205 override void then_(void delegate(Value) nothrow cb) nothrow { 206 cb(value); 207 } 208 }; 209 } 210 211 static Promise!T rejected(Exception e) nothrow { 212 import core.runtime : Runtime; 213 if (e.info is null) { 214 try { 215 e.info = Runtime.traceHandler()(null); 216 } catch(Exception) { 217 } 218 } 219 220 return new class Promise!T { 221 override void then_(void delegate(Value) nothrow cb) nothrow { 222 cb(Value(e)); 223 } 224 }; 225 } 226 227 final Promise!void nothrow_() nothrow { 228 return except((Exception e) => .fatal(e)); 229 } 230 231 protected void then_(void delegate(Value) nothrow cb) nothrow; 232 233 final public void thenWithTrace(void delegate(Value) nothrow cb) nothrow { 234 import upromised.backtrace : backtrace, traceinfo, concat, setBasestack, recoverBasestack; 235 236 Throwable.TraceInfo backBt = ["*async*"].traceinfo.concat(backtrace()); 237 then_((value) nothrow { 238 auto prev = setBasestack(backBt); 239 scope(exit) recoverBasestack(prev); 240 cb(value); 241 }); 242 } 243 } 244 245 class DelegatePromise(T) : Promise!T { 246 bool resolved; 247 Value result; 248 void delegate(Value) nothrow[] pending; 249 250 override void then_(void delegate(Value) nothrow cb) nothrow { 251 if (resolved) { 252 cb(result); 253 } else { 254 pending ~= cb; 255 } 256 } 257 258 void resolve(Value value) nothrow { 259 scope(failure) fatal(); 260 assert(!resolved); 261 result = value; 262 resolved = true; 263 foreach(cb; pending) { 264 cb(result); 265 } 266 pending = null; 267 } 268 269 static if (is(T == void)) { 270 void resolve() nothrow { 271 resolve(Value()); 272 } 273 } else { 274 void resolve(T value) nothrow { 275 resolve(Value(null, value)); 276 } 277 } 278 void reject(Exception e) nothrow { 279 resolve(Value(e)); 280 } 281 } 282 unittest { // Multiple then 283 auto a = new DelegatePromise!int; 284 int sum = 0; 285 a.then((int a) { sum += a; }); 286 a.then((a) { sum += a; }); 287 a.resolve(2); 288 assert(sum == 4); 289 a.then((a) { sum += a; }); 290 assert(sum == 6); 291 } 292 unittest { // Void promise 293 auto a = new DelegatePromise!void; 294 bool called = false; 295 a.then(() { 296 called = true; 297 }).nothrow_(); 298 assert(!called); 299 a.resolve(); 300 assert(called); 301 } 302 303 unittest { // Void Chaining 304 auto a = new DelegatePromise!int; 305 int sum = 0; 306 a.then((a) { 307 sum += a; 308 }).then(() { 309 sum += 3; 310 }).nothrow_(); 311 assert(sum == 0); 312 a.resolve(3); 313 assert(sum == 6); 314 } 315 unittest { // Chaining 316 auto a = new DelegatePromise!int; 317 int delayValue; 318 DelegatePromise!string delayed; 319 string finalValue; 320 321 a.then((a) { 322 return a * 2; 323 }).then((a) { 324 delayed = new DelegatePromise!string; 325 delayValue = a; 326 return delayed; 327 }).then((a) { 328 finalValue = a; 329 }).nothrow_(); 330 a.resolve(1); 331 assert(delayValue == 2); 332 assert(finalValue == ""); 333 delayed.resolve("2"); 334 assert(finalValue == "2"); 335 } 336 unittest { //Exceptions 337 auto a = new DelegatePromise!int; 338 339 auto err = new Exception("yada"); 340 bool caught = false; 341 a.except((Exception e) { 342 assert(err is e); 343 caught = true; 344 }).nothrow_(); 345 assert(!caught); 346 a.reject(err); 347 assert(caught); 348 } 349 unittest { //Exception chaining 350 auto a = new DelegatePromise!int; 351 class X : Exception { 352 this() { 353 super("X"); 354 } 355 } 356 357 bool caught = false; 358 auto err = new Exception("yada"); 359 a.except((X _) { 360 assert(false); 361 }).except((Exception e) { 362 assert(e is err); 363 caught = true; 364 }).nothrow_(); 365 assert(!caught); 366 a.reject(err); 367 assert(caught); 368 } 369 unittest { 370 auto a = new DelegatePromise!int; 371 auto err = new Exception("yada"); 372 bool caught = false; 373 a.then((a) { 374 throw err; 375 }).then(() { 376 assert(false); 377 }).except((Exception e) { 378 assert(e == err); 379 caught = true; 380 }).nothrow_(); 381 assert(!caught); 382 a.resolve(2); 383 assert(caught); 384 } 385 // Finall propagates the other value 386 unittest { 387 bool called; 388 Promise!void.resolved() 389 .then(() => 2) 390 .finall(() => 3) 391 .then((a) { 392 assert(a == 3); 393 return a; 394 }).finall(() { 395 }).then((a) { 396 assert(a == 3); 397 called = true; 398 }).nothrow_(); 399 assert(called); 400 } 401 // Failure method is not called on success 402 unittest { 403 bool called; 404 Promise!int.resolved(3) 405 .failure((Exception _) { 406 assert(false); 407 }).then((a) { 408 assert(a == 3); 409 called = true; 410 }).nothrow_(); 411 assert(called); 412 } 413 // Failure method is called on error 414 unittest { 415 bool called; 416 bool called2; 417 auto err = new Exception("err"); 418 Promise!int.rejected(err) 419 .failure((Exception e) { 420 assert(e is err); 421 called = true; 422 }).except((Exception e) { 423 assert(e is err); 424 called2 = true; 425 }).nothrow_(); 426 assert(called); 427 assert(called2); 428 } 429 // Failure might be delayed 430 unittest { 431 DelegatePromise!void delay; 432 bool called2; 433 auto err = new Exception("err"); 434 Promise!int.rejected(err) 435 .failure((Exception e) { 436 assert(e is err); 437 delay = new DelegatePromise!void; 438 return delay; 439 }).except((Exception e) { 440 assert(e is err); 441 called2 = true; 442 }).nothrow_(); 443 assert(delay !is null); 444 assert(!called2); 445 delay.resolve(); 446 assert(called2); 447 } 448 interface PromiseIterator(T) { 449 struct ItValue { 450 bool eof; 451 T value; 452 } 453 Promise!ItValue next(Promise!bool done = break_); 454 455 final Promise!bool each(U)(U delegate(T) cb) nothrow 456 if (is(Promisify!U.T == void) || is(Promisify!U.T == bool)) 457 { 458 import upromised.backtrace : backtrace, traceinfo, concat, setBasestack, recoverBasestack; 459 460 Throwable.TraceInfo backBt = ["*async*"].traceinfo.concat(backtrace()); 461 462 static if (is(Promisify!U.T == void)) { 463 bool delegate() boolify = () => true; 464 } else { 465 bool delegate(bool) boolify = (bool a) => a; 466 } 467 468 bool eof; 469 return do_while(() { 470 auto done = new DelegatePromise!bool; 471 return promisifyCall(&next, done).then((a) { 472 if (a.eof) { 473 done.resolve(false); 474 eof = true; 475 return break_; 476 } else { 477 auto prev = setBasestack(backBt); 478 scope(exit) recoverBasestack(prev); 479 return promisifyCall(cb, a.value).then(boolify).then((cont) { 480 done.resolve(cont); 481 return cont; 482 }); 483 } 484 }); 485 }).then(() => eof); 486 } 487 } 488 class DelegatePromiseIterator(T) : PromiseIterator!T { 489 import std.typecons : Tuple, tuple; 490 491 alias Value = Promise!ItValue.Value; 492 Tuple!(DelegatePromise!ItValue, Promise!bool) pending; 493 Tuple!(Promise!ItValue, DelegatePromise!bool)[] buffer; 494 495 override Promise!ItValue next(Promise!bool done) { 496 if (buffer.length > 0) { 497 auto next = buffer[0]; 498 buffer = buffer[1..$]; 499 done.thenWithTrace(a => next[1].resolve(a)); 500 return next[0]; 501 } else { 502 assert(pending[0] is null); 503 pending = tuple(new DelegatePromise!ItValue, done); 504 return pending[0]; 505 } 506 } 507 508 Promise!bool resolve(T a) nothrow { 509 return resolve(Value(null, ItValue(false, a))); 510 } 511 512 Promise!bool resolve() nothrow { 513 return resolve(Value(null, ItValue(true))); 514 } 515 516 Promise!bool reject(Exception e) nothrow { 517 return resolve(Value(e)); 518 } 519 520 Promise!bool resolve(Value a) nothrow { 521 auto cb = getPending; 522 if (cb[0]) { 523 cb[0].resolve(a); 524 return cb[1]; 525 } else { 526 auto r = new DelegatePromise!bool; 527 buffer ~= tuple(Promise!ItValue.resolved_(a), r); 528 return r; 529 } 530 } 531 532 Tuple!(DelegatePromise!ItValue, Promise!bool) getPending() nothrow { 533 auto r = pending; 534 pending[0] = null; 535 return r; 536 } 537 } 538 539 unittest { //Iterator 540 DelegatePromiseIterator!int a = new DelegatePromiseIterator!int; 541 int sum = 0; 542 a.resolve(1); 543 assert(sum == 0); 544 a.each((b) { 545 sum += b; 546 return true; 547 }).then((bool) { 548 sum = 0; 549 return; 550 }).nothrow_(); 551 assert(sum == 1); 552 a.resolve(2); 553 assert(sum == 3); 554 a.resolve(); 555 assert(sum == 0); 556 } 557 unittest { //Iterator catching 558 DelegatePromiseIterator!int a = new DelegatePromiseIterator!int; 559 auto err = new Exception("yada"); 560 bool caught = false; 561 a.each((b) { 562 throw err; 563 }).except((Exception e) { 564 assert(e == err); 565 caught = true; 566 }).nothrow_(); 567 assert(!caught); 568 a.resolve(2); 569 assert(caught); 570 caught = false; 571 a.resolve(3); 572 assert(!caught); 573 a.each((b) { 574 assert(b == 3); 575 caught = true; 576 }); 577 assert(caught); 578 } 579 unittest { //Resolve done Promise 580 auto a = new DelegatePromiseIterator!int; 581 bool done = false; 582 a.resolve(2).then((a) { 583 assert(!a); 584 done = true; 585 }).nothrow_(); 586 assert(!done); 587 a.each((a) { 588 return false; 589 }); 590 assert(done); 591 done = false; 592 DelegatePromise!bool delayed; 593 a.each((a) { 594 delayed = new DelegatePromise!bool; 595 return delayed; 596 }).nothrow_(); 597 assert(!done); 598 bool done2 = false; 599 a.resolve(3).then((a) { 600 assert(a); 601 done = true; 602 }).nothrow_(); 603 a.resolve(4).then((a) { 604 assert(!a); 605 done2 = true; 606 }).nothrow_(); 607 assert(!done); 608 assert(!done2); 609 delayed.resolve(true); 610 assert(done); 611 assert(!done2); 612 delayed.resolve(false); 613 assert(done); 614 assert(done2); 615 } 616 unittest { // Rejecting iterator 617 auto a = new DelegatePromiseIterator!int; 618 bool called = false; 619 auto err = new Exception("yada"); 620 class X : Exception { 621 this() { 622 super("yada"); 623 } 624 } 625 a.each((a) { 626 assert(false); 627 }).except((X err) { 628 assert(false); 629 }).except((Exception e) { 630 assert(err is e); 631 called = true; 632 }).nothrow_(); 633 assert(!called); 634 a.reject(err); 635 assert(called); 636 } 637 unittest { //Resolved and rejected promise constructor 638 bool called = false; 639 Promise!void.resolved().then(() { 640 called = true; 641 }); 642 assert(called); 643 called = false; 644 Promise!int.resolved(3).then((a) { 645 assert(a == 3); 646 called = true; 647 }).nothrow_(); 648 assert(called); 649 called = false; 650 auto err = new Exception("yada"); 651 Promise!int.rejected(err).except((Exception e) { 652 assert(e is err); 653 called = true; 654 }).nothrow_(); 655 assert(called); 656 } 657 unittest { //Finally 658 auto a = new DelegatePromise!int; 659 auto called = [false,false,false]; 660 auto err = new Exception("yada"); 661 a.then((a) { 662 }).except((Exception e) { 663 assert(false); 664 }).finall(() { 665 called[0] = true; 666 }).then(() { 667 throw err; 668 }).finall(() { 669 called[1] = true; 670 }).except((Exception e) { 671 assert(called[1]); 672 called[2] = true; 673 assert(e is err); 674 }).nothrow_(); 675 assert(!called[0]); 676 assert(!called[1]); 677 assert(!called[2]); 678 a.resolve(1); 679 assert(called[0]); 680 assert(called[1]); 681 assert(called[2]); 682 } 683 unittest { //Finally might throw Exception 684 auto a = new DelegatePromise!int; 685 auto called = false; 686 auto err = new Exception("yada"); 687 a.finall(() { 688 throw err; 689 }).then(() { 690 assert(false); 691 }).except((Exception e) { 692 assert(e is err); 693 called = true; 694 }).nothrow_(); 695 assert(!called); 696 a.resolve(2); 697 assert(called); 698 } 699 unittest { //Finally return promise 700 auto a = new DelegatePromise!int; 701 auto called = false; 702 auto err = new Exception("yada"); 703 a.finall(() { 704 return Promise!void.rejected(err); 705 }).then(() { 706 assert(false); 707 }).except((Exception e) { 708 assert(err is e); 709 called = true; 710 }).nothrow_(); 711 assert(!called); 712 a.resolve(2); 713 assert(called); 714 } 715 unittest { //Return failed promise 716 auto a = new DelegatePromise!int; 717 auto called = false; 718 auto err = new Exception("yada"); 719 a.then((a) { 720 return Promise!void.rejected(err); 721 }).then(() { 722 assert(false); 723 }).except((Exception e) { 724 assert(err is e); 725 called = true; 726 }).nothrow_(); 727 assert(!called); 728 a.resolve(2); 729 assert(called); 730 } 731 unittest { //Finally returning a value 732 auto a = new DelegatePromise!int; 733 bool called = false; 734 a.finall(() { 735 return 2; 736 }).then((a) { 737 assert(a == 2); 738 called = true; 739 }).nothrow_(); 740 assert(!called); 741 a.resolve(0); 742 assert(called); 743 } 744 unittest { //Fail right away 745 auto a = new DelegatePromiseIterator!int; 746 auto err = new Exception("yada"); 747 bool called = false; 748 a.reject(err); 749 a.each((a) { 750 assert(false); 751 }).except((Exception e) { 752 assert(e is err); 753 called = true; 754 }).nothrow_(); 755 assert(called); 756 } 757 unittest { //EOF right away 758 auto a = new DelegatePromiseIterator!int; 759 bool called = false; 760 a.resolve(); 761 a.each((a) { 762 assert(false); 763 }).then((eof) { 764 assert(eof); 765 called = true; 766 }).nothrow_(); 767 assert(called); 768 } 769 unittest { // Ones might re-resolve right away 770 auto a = new DelegatePromiseIterator!int; 771 int calls = 0; 772 auto cont_delay = new DelegatePromise!bool; 773 774 a.resolve(0).then((cont) { 775 a.resolve(1); 776 }).nothrow_(); 777 778 a.each((b) { 779 assert(b == calls); 780 calls++; 781 782 if (b == 0) { 783 return Promise!bool.resolved(true); 784 } else { 785 return cont_delay; 786 } 787 }).then((eof) { 788 assert(eof); 789 assert(calls == 2); 790 calls++; 791 }).nothrow_(); 792 793 a.resolve(); 794 assert(calls == 2); 795 cont_delay.resolve(true); 796 } 797 // next() might throw exception 798 unittest { 799 auto err = new Exception("oi"); 800 auto x = new class PromiseIterator!int { 801 override Promise!ItValue next(Promise!bool) { 802 throw err; 803 } 804 }; 805 806 bool called = false; 807 x.each((_) { 808 assert(false); 809 }).then((_) { 810 assert(false); 811 }).except((Exception e) { 812 called = true; 813 assert(e is err); 814 }).nothrow_(); 815 assert(called); 816 } 817 private void do_while(U)(U delegate() cb, DelegatePromise!void r, Throwable.TraceInfo backBt) nothrow { 818 import upromised.backtrace : setBasestack, recoverBasestack; 819 820 promisifyCall(cb).then_((v) nothrow { 821 auto prev = setBasestack(backBt); 822 scope(exit) recoverBasestack(prev); 823 824 if (v.e) { 825 r.resolve(Promise!void.Value(v.e)); 826 return; 827 } 828 829 bool cont; 830 static if(is(Promisify!U.T == void)) { 831 cont = true; 832 } else { 833 cont = v.value[0]; 834 } 835 836 if (cont) { 837 do_while(cb, r, backBt); 838 } else { 839 r.resolve(); 840 } 841 }); 842 } 843 Promise!void do_while(U)(U delegate() cb) nothrow 844 if (is(Promisify!U.T == bool) || is(Promisify!U.T == void)) 845 { 846 import upromised.backtrace : backtrace, traceinfo, concat; 847 848 auto r = new DelegatePromise!void; 849 Throwable.TraceInfo backBt = ["*async*"].traceinfo.concat(backtrace()); 850 do_while(cb, r, backBt); 851 return r; 852 } 853 static Promise!bool break_; 854 static Promise!bool continue_; 855 static this() { 856 break_ = Promise!bool.resolved(false); 857 continue_ = Promise!bool.resolved(true); 858 } 859 // do_while until return 0 860 unittest { 861 int count = 3; 862 do_while(() { 863 count--; 864 return count > 0; 865 }).nothrow_(); 866 867 assert(count == 0); 868 } 869 // do_while might be delayed 870 unittest { 871 int called = 0; 872 DelegatePromise!bool next; 873 do_while(() { 874 ++called; 875 next = new DelegatePromise!bool; 876 return next; 877 }).then(() { 878 assert(called == 2); 879 called++; 880 }).nothrow_(); 881 assert(called == 1); 882 next.resolve(true); 883 assert(called == 2); 884 next.resolve(false); 885 assert(called == 3); 886 } 887 // do_while catches all exceptions 888 unittest { 889 auto err = new Exception(""); 890 bool called; 891 do_while(() { 892 throw err; 893 }).except((Exception e) { 894 assert(e is err); 895 called = true; 896 }).nothrow_(); 897 assert(called); 898 } 899 // do_while with void loops indefinitely 900 unittest { 901 int called; 902 DelegatePromise!void next; 903 do_while(() { 904 called++; 905 next = new DelegatePromise!void; 906 return next; 907 }).nothrow_(); 908 assert(called == 1); 909 next.resolve(); 910 assert(called == 2); 911 next.resolve(); 912 assert(called == 3); 913 }