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