@b9g/crank 0.5.0-beta.3 → 0.5.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/crank.js CHANGED
@@ -732,12 +732,6 @@ const NeedsToYield = 1 << 3;
732
732
  * resumeCtxIterator function.
733
733
  */
734
734
  const PropsAvailable = 1 << 4;
735
- /**
736
- * A flag which is set when a generator components returns, i.e. the done
737
- * property on the iteration is true. Generator components will stick to their
738
- * last rendered value and ignore further updates.
739
- */
740
- const IsDone = 1 << 5;
741
735
  /**
742
736
  * A flag which is set when a component errors.
743
737
  *
@@ -781,7 +775,7 @@ const flushMaps = new WeakMap();
781
775
  class ContextImpl {
782
776
  constructor(renderer, root, host, parent, scope, ret) {
783
777
  this.f = 0;
784
- this.ctx = new Context(this);
778
+ this.owner = new Context(this);
785
779
  this.renderer = renderer;
786
780
  this.root = root;
787
781
  this.host = host;
@@ -838,65 +832,65 @@ class Context {
838
832
  return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
839
833
  }
840
834
  *[Symbol.iterator]() {
841
- const impl = this[_ContextImpl];
842
- if (impl.f & IsAsyncGen) {
835
+ const ctx = this[_ContextImpl];
836
+ if (ctx.f & IsAsyncGen) {
843
837
  throw new Error("Use for await…of in async generator components");
844
838
  }
845
839
  try {
846
- impl.f |= IsInRenderLoop;
847
- while (!(impl.f & IsUnmounted)) {
848
- if (impl.f & NeedsToYield) {
840
+ ctx.f |= IsInRenderLoop;
841
+ while (!(ctx.f & IsUnmounted)) {
842
+ if (ctx.f & NeedsToYield) {
849
843
  throw new Error("Context iterated twice without a yield");
850
844
  }
851
845
  else {
852
- impl.f |= NeedsToYield;
846
+ ctx.f |= NeedsToYield;
853
847
  }
854
- yield impl.ret.el.props;
848
+ yield ctx.ret.el.props;
855
849
  }
856
850
  }
857
851
  finally {
858
- impl.f &= ~IsInRenderLoop;
852
+ ctx.f &= ~IsInRenderLoop;
859
853
  }
860
854
  }
861
855
  async *[Symbol.asyncIterator]() {
862
- const impl = this[_ContextImpl];
863
- if (impl.f & IsSyncGen) {
856
+ const ctx = this[_ContextImpl];
857
+ if (ctx.f & IsSyncGen) {
864
858
  throw new Error("Use for…of in sync generator components");
865
859
  }
866
860
  try {
867
861
  // await an empty promise to prevent the IsInRenderLoop flag from
868
862
  // returning false positives in the case of async generator components
869
863
  // which immediately enter the loop
870
- impl.f |= IsInRenderLoop;
871
- while (!(impl.f & IsUnmounted)) {
872
- if (impl.f & NeedsToYield) {
864
+ ctx.f |= IsInRenderLoop;
865
+ while (!(ctx.f & IsUnmounted)) {
866
+ if (ctx.f & NeedsToYield) {
873
867
  throw new Error("Context iterated twice without a yield");
874
868
  }
875
869
  else {
876
- impl.f |= NeedsToYield;
870
+ ctx.f |= NeedsToYield;
877
871
  }
878
- if (impl.f & PropsAvailable) {
879
- impl.f &= ~PropsAvailable;
880
- yield impl.ret.el.props;
872
+ if (ctx.f & PropsAvailable) {
873
+ ctx.f &= ~PropsAvailable;
874
+ yield ctx.ret.el.props;
881
875
  }
882
876
  else {
883
- const props = await new Promise((resolve) => (impl.onProps = resolve));
884
- if (impl.f & IsUnmounted) {
877
+ const props = await new Promise((resolve) => (ctx.onProps = resolve));
878
+ if (ctx.f & IsUnmounted) {
885
879
  break;
886
880
  }
887
881
  yield props;
888
882
  }
889
- if (impl.onPropsRequested) {
890
- impl.onPropsRequested();
891
- impl.onPropsRequested = undefined;
883
+ if (ctx.onPropsRequested) {
884
+ ctx.onPropsRequested();
885
+ ctx.onPropsRequested = undefined;
892
886
  }
893
887
  }
894
888
  }
895
889
  finally {
896
- impl.f &= ~IsInRenderLoop;
897
- if (impl.onPropsRequested) {
898
- impl.onPropsRequested();
899
- impl.onPropsRequested = undefined;
890
+ ctx.f &= ~IsInRenderLoop;
891
+ if (ctx.onPropsRequested) {
892
+ ctx.onPropsRequested();
893
+ ctx.onPropsRequested = undefined;
900
894
  }
901
895
  }
902
896
  }
@@ -913,31 +907,31 @@ class Context {
913
907
  * async iterator to suspend.
914
908
  */
915
909
  refresh() {
916
- const impl = this[_ContextImpl];
917
- if (impl.f & IsUnmounted) {
910
+ const ctx = this[_ContextImpl];
911
+ if (ctx.f & IsUnmounted) {
918
912
  console.error("Component is unmounted");
919
- return impl.renderer.read(undefined);
913
+ return ctx.renderer.read(undefined);
920
914
  }
921
- else if (impl.f & IsSyncExecuting) {
915
+ else if (ctx.f & IsSyncExecuting) {
922
916
  console.error("Component is already executing");
923
917
  return this.value;
924
918
  }
925
- const value = enqueueComponentRun(impl);
919
+ const value = enqueueComponentRun(ctx);
926
920
  if (isPromiseLike(value)) {
927
- return value.then((value) => impl.renderer.read(value));
921
+ return value.then((value) => ctx.renderer.read(value));
928
922
  }
929
- return impl.renderer.read(value);
923
+ return ctx.renderer.read(value);
930
924
  }
931
925
  /**
932
926
  * Registers a callback which fires when the component commits. Will only
933
927
  * fire once per callback and update.
934
928
  */
935
929
  schedule(callback) {
936
- const impl = this[_ContextImpl];
937
- let callbacks = scheduleMap.get(impl);
930
+ const ctx = this[_ContextImpl];
931
+ let callbacks = scheduleMap.get(ctx);
938
932
  if (!callbacks) {
939
933
  callbacks = new Set();
940
- scheduleMap.set(impl, callbacks);
934
+ scheduleMap.set(ctx, callbacks);
941
935
  }
942
936
  callbacks.add(callback);
943
937
  }
@@ -946,19 +940,19 @@ class Context {
946
940
  * rendered into the root. Will only fire once per callback and render.
947
941
  */
948
942
  flush(callback) {
949
- const impl = this[_ContextImpl];
950
- if (typeof impl.root !== "object" || impl.root === null) {
943
+ const ctx = this[_ContextImpl];
944
+ if (typeof ctx.root !== "object" || ctx.root === null) {
951
945
  return;
952
946
  }
953
- let flushMap = flushMaps.get(impl.root);
947
+ let flushMap = flushMaps.get(ctx.root);
954
948
  if (!flushMap) {
955
949
  flushMap = new Map();
956
- flushMaps.set(impl.root, flushMap);
950
+ flushMaps.set(ctx.root, flushMap);
957
951
  }
958
- let callbacks = flushMap.get(impl);
952
+ let callbacks = flushMap.get(ctx);
959
953
  if (!callbacks) {
960
954
  callbacks = new Set();
961
- flushMap.set(impl, callbacks);
955
+ flushMap.set(ctx, callbacks);
962
956
  }
963
957
  callbacks.add(callback);
964
958
  }
@@ -967,45 +961,45 @@ class Context {
967
961
  * fire once per callback.
968
962
  */
969
963
  cleanup(callback) {
970
- const impl = this[_ContextImpl];
971
- let callbacks = cleanupMap.get(impl);
964
+ const ctx = this[_ContextImpl];
965
+ let callbacks = cleanupMap.get(ctx);
972
966
  if (!callbacks) {
973
967
  callbacks = new Set();
974
- cleanupMap.set(impl, callbacks);
968
+ cleanupMap.set(ctx, callbacks);
975
969
  }
976
970
  callbacks.add(callback);
977
971
  }
978
972
  consume(key) {
979
- for (let parent = this[_ContextImpl].parent; parent !== undefined; parent = parent.parent) {
980
- const provisions = provisionMaps.get(parent);
973
+ for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
974
+ const provisions = provisionMaps.get(ctx);
981
975
  if (provisions && provisions.has(key)) {
982
976
  return provisions.get(key);
983
977
  }
984
978
  }
985
979
  }
986
980
  provide(key, value) {
987
- const impl = this[_ContextImpl];
988
- let provisions = provisionMaps.get(impl);
981
+ const ctx = this[_ContextImpl];
982
+ let provisions = provisionMaps.get(ctx);
989
983
  if (!provisions) {
990
984
  provisions = new Map();
991
- provisionMaps.set(impl, provisions);
985
+ provisionMaps.set(ctx, provisions);
992
986
  }
993
987
  provisions.set(key, value);
994
988
  }
995
989
  addEventListener(type, listener, options) {
996
- const impl = this[_ContextImpl];
990
+ const ctx = this[_ContextImpl];
997
991
  let listeners;
998
992
  if (!isListenerOrListenerObject(listener)) {
999
993
  return;
1000
994
  }
1001
995
  else {
1002
- const listeners1 = listenersMap.get(impl);
996
+ const listeners1 = listenersMap.get(ctx);
1003
997
  if (listeners1) {
1004
998
  listeners = listeners1;
1005
999
  }
1006
1000
  else {
1007
1001
  listeners = [];
1008
- listenersMap.set(impl, listeners);
1002
+ listenersMap.set(ctx, listeners);
1009
1003
  }
1010
1004
  }
1011
1005
  options = normalizeListenerOptions(options);
@@ -1016,7 +1010,7 @@ class Context {
1016
1010
  else {
1017
1011
  callback = listener;
1018
1012
  }
1019
- const record = { type, callback, listener, options };
1013
+ const record = { type, listener, callback, options };
1020
1014
  if (options.once) {
1021
1015
  record.callback = function () {
1022
1016
  const i = listeners.indexOf(record);
@@ -1033,15 +1027,15 @@ class Context {
1033
1027
  }
1034
1028
  listeners.push(record);
1035
1029
  // TODO: is it possible to separate out the EventTarget delegation logic
1036
- for (const value of getChildValues(impl.ret)) {
1030
+ for (const value of getChildValues(ctx.ret)) {
1037
1031
  if (isEventTarget(value)) {
1038
1032
  value.addEventListener(record.type, record.callback, record.options);
1039
1033
  }
1040
1034
  }
1041
1035
  }
1042
1036
  removeEventListener(type, listener, options) {
1043
- const impl = this[_ContextImpl];
1044
- const listeners = listenersMap.get(impl);
1037
+ const ctx = this[_ContextImpl];
1038
+ const listeners = listenersMap.get(ctx);
1045
1039
  if (listeners == null || !isListenerOrListenerObject(listener)) {
1046
1040
  return;
1047
1041
  }
@@ -1055,16 +1049,16 @@ class Context {
1055
1049
  const record = listeners[i];
1056
1050
  listeners.splice(i, 1);
1057
1051
  // TODO: is it possible to separate out the EventTarget delegation logic
1058
- for (const value of getChildValues(impl.ret)) {
1052
+ for (const value of getChildValues(ctx.ret)) {
1059
1053
  if (isEventTarget(value)) {
1060
1054
  value.removeEventListener(record.type, record.callback, record.options);
1061
1055
  }
1062
1056
  }
1063
1057
  }
1064
1058
  dispatchEvent(ev) {
1065
- const impl = this[_ContextImpl];
1059
+ const ctx = this[_ContextImpl];
1066
1060
  const path = [];
1067
- for (let parent = impl.parent; parent !== undefined; parent = parent.parent) {
1061
+ for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
1068
1062
  path.push(parent);
1069
1063
  }
1070
1064
  // We patch the stopImmediatePropagation method because ev.cancelBubble
@@ -1076,7 +1070,7 @@ class Context {
1076
1070
  immediateCancelBubble = true;
1077
1071
  return stopImmediatePropagation.call(ev);
1078
1072
  });
1079
- setEventProperty(ev, "target", impl.ctx);
1073
+ setEventProperty(ev, "target", ctx.owner);
1080
1074
  // The only possible errors in this block are errors thrown by callbacks,
1081
1075
  // and dispatchEvent will only log these errors rather than throwing
1082
1076
  // them. Therefore, we place all code in a try block, log errors in the
@@ -1085,18 +1079,21 @@ class Context {
1085
1079
  // Each early return within the try block returns true because while the
1086
1080
  // return value is overridden in the finally block, TypeScript
1087
1081
  // (justifiably) does not recognize the unsafe return statement.
1088
- //
1089
- // TODO: Run all callbacks even if one of them errors
1090
1082
  try {
1091
1083
  setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
1092
1084
  for (let i = path.length - 1; i >= 0; i--) {
1093
1085
  const target = path[i];
1094
1086
  const listeners = listenersMap.get(target);
1095
1087
  if (listeners) {
1096
- setEventProperty(ev, "currentTarget", target.ctx);
1088
+ setEventProperty(ev, "currentTarget", target.owner);
1097
1089
  for (const record of listeners) {
1098
1090
  if (record.type === ev.type && record.options.capture) {
1099
- record.callback.call(target.ctx, ev);
1091
+ try {
1092
+ record.callback.call(target.owner, ev);
1093
+ }
1094
+ catch (err) {
1095
+ console.error(err);
1096
+ }
1100
1097
  if (immediateCancelBubble) {
1101
1098
  return true;
1102
1099
  }
@@ -1108,13 +1105,25 @@ class Context {
1108
1105
  }
1109
1106
  }
1110
1107
  {
1111
- const listeners = listenersMap.get(impl);
1108
+ setEventProperty(ev, "eventPhase", AT_TARGET);
1109
+ setEventProperty(ev, "currentTarget", ctx.owner);
1110
+ const propCallback = ctx.ret.el.props["on" + ev.type];
1111
+ if (propCallback != null) {
1112
+ propCallback(ev);
1113
+ if (immediateCancelBubble || ev.cancelBubble) {
1114
+ return true;
1115
+ }
1116
+ }
1117
+ const listeners = listenersMap.get(ctx);
1112
1118
  if (listeners) {
1113
- setEventProperty(ev, "eventPhase", AT_TARGET);
1114
- setEventProperty(ev, "currentTarget", impl.ctx);
1115
1119
  for (const record of listeners) {
1116
1120
  if (record.type === ev.type) {
1117
- record.callback.call(impl.ctx, ev);
1121
+ try {
1122
+ record.callback.call(ctx.owner, ev);
1123
+ }
1124
+ catch (err) {
1125
+ console.error(err);
1126
+ }
1118
1127
  if (immediateCancelBubble) {
1119
1128
  return true;
1120
1129
  }
@@ -1131,10 +1140,15 @@ class Context {
1131
1140
  const target = path[i];
1132
1141
  const listeners = listenersMap.get(target);
1133
1142
  if (listeners) {
1134
- setEventProperty(ev, "currentTarget", target.ctx);
1143
+ setEventProperty(ev, "currentTarget", target.owner);
1135
1144
  for (const record of listeners) {
1136
1145
  if (record.type === ev.type && !record.options.capture) {
1137
- record.callback.call(target.ctx, ev);
1146
+ try {
1147
+ record.callback.call(target.owner, ev);
1148
+ }
1149
+ catch (err) {
1150
+ console.error(err);
1151
+ }
1138
1152
  if (immediateCancelBubble) {
1139
1153
  return true;
1140
1154
  }
@@ -1147,10 +1161,6 @@ class Context {
1147
1161
  }
1148
1162
  }
1149
1163
  }
1150
- catch (err) {
1151
- // TODO: Use setTimeout to rethrow the error.
1152
- console.error(err);
1153
- }
1154
1164
  finally {
1155
1165
  setEventProperty(ev, "eventPhase", NONE);
1156
1166
  setEventProperty(ev, "currentTarget", null);
@@ -1184,7 +1194,12 @@ function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
1184
1194
  return enqueueComponentRun(ctx);
1185
1195
  }
1186
1196
  function updateComponentChildren(ctx, children) {
1187
- if (ctx.f & IsUnmounted || ctx.f & IsErrored) {
1197
+ if (ctx.f & IsUnmounted) {
1198
+ return;
1199
+ }
1200
+ else if (ctx.f & IsErrored) {
1201
+ // This branch is necessary for some race conditions where this function is
1202
+ // called after iterator.throw() in async generator components.
1188
1203
  return;
1189
1204
  }
1190
1205
  else if (children === undefined) {
@@ -1411,9 +1426,6 @@ function advanceComponent(ctx) {
1411
1426
  */
1412
1427
  function runComponent(ctx) {
1413
1428
  const ret = ctx.ret;
1414
- if (ctx.f & IsDone) {
1415
- return [undefined, getValue(ret)];
1416
- }
1417
1429
  const initial = !ctx.iterator;
1418
1430
  if (initial) {
1419
1431
  resumePropsIterator(ctx);
@@ -1421,7 +1433,7 @@ function runComponent(ctx) {
1421
1433
  clearEventListeners(ctx);
1422
1434
  let result;
1423
1435
  try {
1424
- result = ret.el.tag.call(ctx.ctx, ret.el.props);
1436
+ result = ret.el.tag.call(ctx.owner, ret.el.props);
1425
1437
  }
1426
1438
  catch (err) {
1427
1439
  ctx.f |= IsErrored;
@@ -1454,7 +1466,7 @@ function runComponent(ctx) {
1454
1466
  iteration = ctx.iterator.next();
1455
1467
  }
1456
1468
  catch (err) {
1457
- ctx.f |= IsDone | IsErrored;
1469
+ ctx.f |= IsErrored;
1458
1470
  throw err;
1459
1471
  }
1460
1472
  finally {
@@ -1474,11 +1486,10 @@ function runComponent(ctx) {
1474
1486
  if (!initial) {
1475
1487
  try {
1476
1488
  ctx.f |= IsSyncExecuting;
1477
- const oldValue = ctx.renderer.read(getValue(ret));
1478
- iteration = ctx.iterator.next(oldValue);
1489
+ iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1479
1490
  }
1480
1491
  catch (err) {
1481
- ctx.f |= IsDone | IsErrored;
1492
+ ctx.f |= IsErrored;
1482
1493
  throw err;
1483
1494
  }
1484
1495
  finally {
@@ -1489,7 +1500,8 @@ function runComponent(ctx) {
1489
1500
  throw new Error("Sync generator component returned an async iteration");
1490
1501
  }
1491
1502
  if (iteration.done) {
1492
- ctx.f |= IsDone;
1503
+ ctx.f &= ~IsSyncGen;
1504
+ ctx.iterator = undefined;
1493
1505
  }
1494
1506
  let value;
1495
1507
  try {
@@ -1503,10 +1515,8 @@ function runComponent(ctx) {
1503
1515
  catch (err) {
1504
1516
  value = handleChildError(ctx, err);
1505
1517
  }
1506
- if (isPromiseLike(value)) {
1507
- return [value.catch(NOOP), value];
1508
- }
1509
- return [undefined, value];
1518
+ const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
1519
+ return [block, value];
1510
1520
  }
1511
1521
  else {
1512
1522
  // async generator component
@@ -1514,65 +1524,80 @@ function runComponent(ctx) {
1514
1524
  }
1515
1525
  }
1516
1526
  async function runAsyncGenComponent(ctx, iterationP) {
1517
- do {
1518
- // block and value must be assigned at the same time.
1519
- let onValue;
1520
- ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
1521
- if (ctx.f & IsUpdating) {
1522
- // We should not swallow unhandled promise rejections if the component is
1523
- // updating independently.
1524
- // TODO: Does this handle this.refresh() calls?
1525
- ctx.inflightValue.catch(NOOP);
1526
- }
1527
- let iteration;
1528
- try {
1529
- iteration = await iterationP;
1530
- }
1531
- catch (err) {
1532
- ctx.f |= IsDone;
1533
- ctx.f |= IsErrored;
1534
- onValue(Promise.reject(err));
1535
- break;
1536
- }
1537
- finally {
1538
- if (!(ctx.f & IsInRenderLoop)) {
1539
- ctx.f &= ~PropsAvailable;
1527
+ let done = false;
1528
+ try {
1529
+ while (!done) {
1530
+ // inflightValue must be set synchronously.
1531
+ let onValue;
1532
+ ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
1533
+ if (ctx.f & IsUpdating) {
1534
+ // We should not swallow unhandled promise rejections if the component is
1535
+ // updating independently.
1536
+ // TODO: Does this handle this.refresh() calls?
1537
+ ctx.inflightValue.catch(NOOP);
1540
1538
  }
1541
- }
1542
- if (ctx.f & NeedsToYield) {
1543
- ctx.f &= ~NeedsToYield;
1544
- }
1545
- if (iteration.done) {
1546
- ctx.f |= IsDone;
1547
- }
1548
- let value;
1549
- try {
1550
- value = updateComponentChildren(ctx, iteration.value);
1551
- if (isPromiseLike(value)) {
1552
- value = value.catch((err) => handleChildError(ctx, err));
1539
+ let iteration;
1540
+ try {
1541
+ iteration = await iterationP;
1553
1542
  }
1554
- onValue(value);
1555
- }
1556
- catch (err) {
1557
- // Do we need to catch potential errors here in the case of unhandled
1558
- // promise rejections?
1559
- value = handleChildError(ctx, err);
1560
- }
1561
- // TODO: this can be done more elegantly
1562
- let oldValue;
1563
- if (ctx.ret.inflightValue) {
1564
- // The value passed back into the generator as the argument to the next
1565
- // method is a promise if an async generator component has async
1566
- // children. Sync generator components only resume when their children
1567
- // have fulfilled so the element’s inflight child values will never be
1568
- // defined.
1569
- oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
1570
- }
1571
- else {
1572
- oldValue = ctx.renderer.read(getValue(ctx.ret));
1573
- }
1574
- if (ctx.f & IsUnmounted) {
1575
- if (ctx.f & IsInRenderLoop) {
1543
+ catch (err) {
1544
+ done = true;
1545
+ ctx.f |= IsErrored;
1546
+ onValue(Promise.reject(err));
1547
+ break;
1548
+ }
1549
+ finally {
1550
+ ctx.f &= ~NeedsToYield;
1551
+ if (!(ctx.f & IsInRenderLoop)) {
1552
+ ctx.f &= ~PropsAvailable;
1553
+ }
1554
+ }
1555
+ done = !!iteration.done;
1556
+ let value;
1557
+ try {
1558
+ value = updateComponentChildren(ctx, iteration.value);
1559
+ if (isPromiseLike(value)) {
1560
+ value = value.catch((err) => handleChildError(ctx, err));
1561
+ }
1562
+ }
1563
+ catch (err) {
1564
+ done = true;
1565
+ // Do we need to catch potential errors here in the case of unhandled
1566
+ // promise rejections?
1567
+ value = handleChildError(ctx, err);
1568
+ }
1569
+ finally {
1570
+ onValue(value);
1571
+ }
1572
+ // TODO: this can be done more elegantly
1573
+ let oldValue;
1574
+ if (ctx.ret.inflightValue) {
1575
+ // The value passed back into the generator as the argument to the next
1576
+ // method is a promise if an async generator component has async
1577
+ // children. Sync generator components only resume when their children
1578
+ // have fulfilled so the element’s inflight child values will never be
1579
+ // defined.
1580
+ oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
1581
+ }
1582
+ else {
1583
+ oldValue = ctx.renderer.read(getValue(ctx.ret));
1584
+ }
1585
+ if (ctx.f & IsUnmounted) {
1586
+ if (ctx.f & IsInRenderLoop) {
1587
+ try {
1588
+ ctx.f |= IsSyncExecuting;
1589
+ iterationP = ctx.iterator.next(oldValue);
1590
+ }
1591
+ finally {
1592
+ ctx.f &= ~IsSyncExecuting;
1593
+ }
1594
+ }
1595
+ else {
1596
+ returnComponent(ctx);
1597
+ break;
1598
+ }
1599
+ }
1600
+ else if (!done) {
1576
1601
  try {
1577
1602
  ctx.f |= IsSyncExecuting;
1578
1603
  iterationP = ctx.iterator.next(oldValue);
@@ -1581,25 +1606,15 @@ async function runAsyncGenComponent(ctx, iterationP) {
1581
1606
  ctx.f &= ~IsSyncExecuting;
1582
1607
  }
1583
1608
  }
1584
- else {
1585
- returnComponent(ctx);
1586
- break;
1587
- }
1588
1609
  }
1589
- else if (!(ctx.f & IsDone)) {
1590
- try {
1591
- ctx.f |= IsSyncExecuting;
1592
- iterationP = ctx.iterator.next(oldValue);
1593
- }
1594
- finally {
1595
- ctx.f &= ~IsSyncExecuting;
1596
- }
1597
- }
1598
- } while (!(ctx.f & IsDone));
1610
+ }
1611
+ finally {
1612
+ ctx.f &= ~IsAsyncGen;
1613
+ ctx.iterator = undefined;
1614
+ }
1599
1615
  }
1600
1616
  /**
1601
- * Called to make props available to the props async iterator for async
1602
- * generator components.
1617
+ * Called to resume the props async iterator for async generator components.
1603
1618
  */
1604
1619
  function resumePropsIterator(ctx) {
1605
1620
  if (ctx.onProps) {
@@ -1623,7 +1638,7 @@ function unmountComponent(ctx) {
1623
1638
  }
1624
1639
  }
1625
1640
  ctx.f |= IsUnmounted;
1626
- if (ctx.iterator && !(ctx.f & IsDone)) {
1641
+ if (ctx.iterator) {
1627
1642
  if (ctx.f & IsSyncGen) {
1628
1643
  let value;
1629
1644
  if (ctx.f & IsInRenderLoop) {
@@ -1650,17 +1665,18 @@ function unmountComponent(ctx) {
1650
1665
  }
1651
1666
  }
1652
1667
  }
1653
- else {
1654
- // async generator component
1668
+ else if (ctx.f & IsAsyncGen) {
1669
+ // The logic for unmounting async generator components is in the
1670
+ // runAsyncGenComponent function.
1655
1671
  resumePropsIterator(ctx);
1656
1672
  }
1657
1673
  }
1658
1674
  }
1659
1675
  function returnComponent(ctx) {
1660
1676
  resumePropsIterator(ctx);
1661
- if (!(ctx.f & IsDone) && typeof ctx.iterator.return === "function") {
1662
- ctx.f |= IsSyncExecuting;
1677
+ if (ctx.iterator && typeof ctx.iterator.return === "function") {
1663
1678
  try {
1679
+ ctx.f |= IsSyncExecuting;
1664
1680
  const iteration = ctx.iterator.return();
1665
1681
  if (isPromiseLike(iteration)) {
1666
1682
  iteration.catch((err) => propagateError(ctx.parent, err));
@@ -1670,7 +1686,6 @@ function returnComponent(ctx) {
1670
1686
  ctx.f &= ~IsSyncExecuting;
1671
1687
  }
1672
1688
  }
1673
- ctx.f |= IsDone;
1674
1689
  }
1675
1690
  /*** EVENT TARGET UTILITIES ***/
1676
1691
  // EVENT PHASE CONSTANTS
@@ -1740,11 +1755,8 @@ function clearEventListeners(ctx) {
1740
1755
  }
1741
1756
  }
1742
1757
  /*** ERROR HANDLING UTILITIES ***/
1743
- // TODO: generator components which throw errors should be recoverable
1744
1758
  function handleChildError(ctx, err) {
1745
- if (ctx.f & IsDone ||
1746
- !ctx.iterator ||
1747
- typeof ctx.iterator.throw !== "function") {
1759
+ if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
1748
1760
  throw err;
1749
1761
  }
1750
1762
  resumePropsIterator(ctx);
@@ -1754,7 +1766,7 @@ function handleChildError(ctx, err) {
1754
1766
  iteration = ctx.iterator.throw(err);
1755
1767
  }
1756
1768
  catch (err) {
1757
- ctx.f |= IsDone | IsErrored;
1769
+ ctx.f |= IsErrored;
1758
1770
  throw err;
1759
1771
  }
1760
1772
  finally {
@@ -1763,16 +1775,18 @@ function handleChildError(ctx, err) {
1763
1775
  if (isPromiseLike(iteration)) {
1764
1776
  return iteration.then((iteration) => {
1765
1777
  if (iteration.done) {
1766
- ctx.f |= IsDone;
1778
+ ctx.f &= ~IsAsyncGen;
1779
+ ctx.iterator = undefined;
1767
1780
  }
1768
1781
  return updateComponentChildren(ctx, iteration.value);
1769
1782
  }, (err) => {
1770
- ctx.f |= IsDone | IsErrored;
1783
+ ctx.f |= IsErrored;
1771
1784
  throw err;
1772
1785
  });
1773
1786
  }
1774
1787
  if (iteration.done) {
1775
- ctx.f |= IsDone;
1788
+ ctx.f &= ~IsSyncGen;
1789
+ ctx.iterator = undefined;
1776
1790
  }
1777
1791
  return updateComponentChildren(ctx, iteration.value);
1778
1792
  }