OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // Flags: --harmony-async-iteration --allow-natives-syntax |
| 6 |
| 7 let testFailed = false; |
| 8 let testFailure; |
| 9 |
| 10 function assertThrowsAsync(run, errorType, message) { |
| 11 var actual; |
| 12 var hadValue = false; |
| 13 var hadError = false; |
| 14 var promise = run(); |
| 15 |
| 16 if (typeof promise !== "object" || typeof promise.then !== "function") { |
| 17 throw new MjsUnitAssertionError( |
| 18 "Expected " + run.toString() + |
| 19 " to return a Promise, but it returned " + PrettyPrint(promise)); |
| 20 } |
| 21 |
| 22 promise.then(function(value) { hadValue = true; actual = value; }, |
| 23 function(error) { hadError = true; actual = error; }); |
| 24 |
| 25 assertFalse(hadValue || hadError); |
| 26 |
| 27 %RunMicrotasks(); |
| 28 |
| 29 if (!hadError) { |
| 30 throw new MjsUnitAssertionError( |
| 31 "Expected " + run + "() to throw " + errorType.name + |
| 32 ", but did not throw."); |
| 33 } |
| 34 if (!(actual instanceof errorType)) |
| 35 throw new MjsUnitAssertionError( |
| 36 "Expected " + run + "() to throw " + errorType.name + |
| 37 ", but threw '" + actual + "'"); |
| 38 if (message !== void 0 && actual.message !== message) |
| 39 throw new MjsUnitAssertionError( |
| 40 "Expected " + run + "() to throw '" + message + "', but threw '" + |
| 41 actual.message + "'"); |
| 42 }; |
| 43 |
| 44 class MyError extends Error {}; |
| 45 |
| 46 (async function() { |
| 47 function* gen() { |
| 48 yield "sync value"; |
| 49 try { |
| 50 yield new Promise(function(resolve) { |
| 51 resolve("async value"); |
| 52 }); |
| 53 } catch (error) { |
| 54 throw error; |
| 55 } |
| 56 assertUnreachable("generator is closed"); |
| 57 } |
| 58 let iter = %CreateAsyncFromSyncIterator(gen()); |
| 59 |
| 60 // [Async-from-Sync Iterator] wraps sync iterator values in a Promise |
| 61 let promise = iter.next(); |
| 62 assertInstanceof(promise, Promise); |
| 63 let iter_result = await promise; |
| 64 assertEquals({ value: "sync value", done: false }, iter_result); |
| 65 |
| 66 // [Async-from-Sync Iterator] will wait for resolution of Promise values |
| 67 promise = iter.next(); |
| 68 assertInstanceof(promise, Promise); |
| 69 iter_result = await promise; |
| 70 assertEquals({ value: "async value", done: false }, iter_result); |
| 71 |
| 72 // [Async-from-Sync Iterator].throw delegates to .throw() method of sync |
| 73 // iterator. |
| 74 promise = iter.throw(new MyError("Error#1")); |
| 75 assertInstanceof(promise, Promise); |
| 76 try { |
| 77 await promise; |
| 78 assertUnreachable("promise should be rejected"); |
| 79 } catch (e) { |
| 80 assertInstanceof(e, MyError); |
| 81 assertEquals("Error#1", e.message); |
| 82 } |
| 83 |
| 84 promise = iter.return("generator closed"); |
| 85 assertInstanceof(promise, Promise); |
| 86 iter_result = await promise; |
| 87 assertEquals({ value: "generator closed", done: true }, iter_result); |
| 88 |
| 89 // .next(), .return() and .throw() delegate to sync iterator methods, without |
| 90 // keeping track of the state of the generator. |
| 91 promise = iter.next("unused"); |
| 92 assertInstanceof(promise, Promise); |
| 93 iter_result = await promise; |
| 94 assertEquals({ value: undefined, done: true }, iter_result); |
| 95 |
| 96 promise = iter.throw(new MyError("Error#2")); |
| 97 assertInstanceof(promise, Promise); |
| 98 try { |
| 99 await promise; |
| 100 } catch (e) { |
| 101 assertInstanceof(e, MyError); |
| 102 assertEquals("Error#2", e.message); |
| 103 } |
| 104 |
| 105 promise = iter.return("return-after-completed"); |
| 106 assertInstanceof(promise, Promise); |
| 107 iter_result = await promise; |
| 108 assertEquals({ value: "return-after-completed", done: true }, iter_result); |
| 109 |
| 110 function resolveLater(value) { |
| 111 return new Promise(function(resolve) { |
| 112 Promise.resolve().then(function() { |
| 113 resolve(value); |
| 114 }); |
| 115 }); |
| 116 } |
| 117 |
| 118 function rejectLater(value) { |
| 119 return new Promise(function(resolve, reject) { |
| 120 Promise.resolve().then(function() { |
| 121 reject(value); |
| 122 }); |
| 123 }); |
| 124 } |
| 125 |
| 126 const kNext = 1; |
| 127 const kThrow = 2; |
| 128 const kReturn = 4; |
| 129 const kNextThrows = kNext | 8; |
| 130 const kReturnThrows = kReturn | 16; |
| 131 const kThrowNormal = kThrow | 32; |
| 132 function sync(array, features, log = []) { |
| 133 let i = 0; |
| 134 let methods = { |
| 135 next(sent) { |
| 136 let done = i >= array.length; |
| 137 let value = array[i]; |
| 138 log.push({ method: "next", sent, value, done }); |
| 139 if ((features & kNextThrows) === kNextThrows) throw sent; |
| 140 i++; |
| 141 return { value, done }; |
| 142 }, |
| 143 throw(sent) { |
| 144 let done = i >= array.length; |
| 145 let value = undefined; |
| 146 log.push({ method: "throw", sent, value, done }); |
| 147 if ((features & kThrowNormal) === kThrowNormal) |
| 148 return { value: sent, done }; |
| 149 throw sent; |
| 150 }, |
| 151 return(sent) { |
| 152 let done = true; |
| 153 let value = undefined; |
| 154 log.push({ method: "return", sent, value, done }); |
| 155 if ((features & kReturnThrows) === kReturnThrows) throw sent; |
| 156 return { value, done }; |
| 157 } |
| 158 }; |
| 159 return { |
| 160 [Symbol.iterator]() { return this; }, |
| 161 next: (features & kNext) ? methods.next : undefined, |
| 162 throw: (features & kThrow) ? methods.throw : undefined, |
| 163 return: (features & kReturn) ? methods.return : undefined |
| 164 }; |
| 165 } |
| 166 |
| 167 let log = []; |
| 168 iter = %CreateAsyncFromSyncIterator(sync(["sync-value"], 0)); |
| 169 |
| 170 try { |
| 171 await iter.next(); |
| 172 assertUnreachable("Iterator.next() method is not optional"); |
| 173 } catch (e) { |
| 174 assertInstanceof(e, TypeError); |
| 175 assertEquals([], log); |
| 176 } |
| 177 |
| 178 log = []; |
| 179 iter = %CreateAsyncFromSyncIterator(sync(["sync-value"], kNext, log)); |
| 180 assertEquals({ value: "sync-value", done: false }, await iter.next("a")); |
| 181 assertEquals([ |
| 182 { |
| 183 method: "next", |
| 184 sent: "a", |
| 185 value: "sync-value", |
| 186 done: false |
| 187 } |
| 188 ], log); |
| 189 |
| 190 log = []; |
| 191 let asyncValue = resolveLater("async-value"); |
| 192 iter = %CreateAsyncFromSyncIterator(sync([asyncValue], kNext, log)); |
| 193 assertEquals({ value: "async-value", done: false }, await iter.next("b")); |
| 194 assertEquals([ |
| 195 { |
| 196 method: "next", |
| 197 sent: "b", |
| 198 value: asyncValue, |
| 199 done: false |
| 200 } |
| 201 ], log); |
| 202 |
| 203 // If [sync_iterator].next() produces a rejected Promise or an exception is |
| 204 // thrown, Promise is rejected with thrown/rejected value. |
| 205 log = []; |
| 206 asyncValue = rejectLater("Boo!"); |
| 207 iter = %CreateAsyncFromSyncIterator(sync([asyncValue], kNext, log)); |
| 208 try { |
| 209 await iter.next('c'); |
| 210 assertUnreachable('Expected `iter.next(\'c\') to throw, but did not throw'); |
| 211 } catch (e) { |
| 212 assertEquals("Boo!", e); |
| 213 assertEquals([ |
| 214 { |
| 215 method: 'next', |
| 216 sent: 'c', |
| 217 value: asyncValue, |
| 218 done: false |
| 219 } |
| 220 ], log); |
| 221 } |
| 222 |
| 223 // If [sync_iterator].return() does not exist, return a Promise resolved with |
| 224 // the value `{ value: <<sent value>>, done: true }`. |
| 225 log = []; |
| 226 iter = %CreateAsyncFromSyncIterator(sync(['sync-return'], kNext, log)); |
| 227 assertEquals({ |
| 228 value: 'd', |
| 229 done: true |
| 230 }, await iter.return('d')); |
| 231 |
| 232 // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| 233 // whether [sync_iterator] is completed or not. |
| 234 assertEquals({ |
| 235 value: 'sync-return', |
| 236 done: false |
| 237 }, await iter.next('e')); |
| 238 |
| 239 assertEquals([ |
| 240 { |
| 241 method: 'next', |
| 242 sent: 'e', |
| 243 value: 'sync-return', |
| 244 done: false |
| 245 } |
| 246 ], log); |
| 247 |
| 248 // If [sync_iterator].return() does exist, return a Promise resolved with |
| 249 // the iterator result of [sync_iterator].return(). |
| 250 log = []; |
| 251 iter = %CreateAsyncFromSyncIterator(sync(['sync-return'], |
| 252 kNext|kReturn, log)); |
| 253 assertEquals({ |
| 254 value: undefined, |
| 255 done: true |
| 256 }, await iter.return('f')); |
| 257 |
| 258 // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| 259 // whether [sync_iterator] is completed or not. |
| 260 assertEquals({ |
| 261 value: 'sync-return', |
| 262 done: false |
| 263 }, await iter.next('g')); |
| 264 |
| 265 assertEquals([ |
| 266 { |
| 267 method: 'return', |
| 268 sent: 'f', |
| 269 value: undefined, |
| 270 done: true |
| 271 }, |
| 272 { |
| 273 method: 'next', |
| 274 sent: 'g', |
| 275 value: 'sync-return', |
| 276 done: false |
| 277 } |
| 278 ], log); |
| 279 |
| 280 // If [sync_iterator].return() produces a rejected Promise or an exception is |
| 281 // thrown, Promise is rejected with thrown/rejected value. |
| 282 log = []; |
| 283 iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kReturnThrows, |
| 284 log)); |
| 285 try { |
| 286 await iter.return('Boo!!'); |
| 287 assertUnreachable('Expected `iter.return(\'Boo!!\') to throw, but did ' + |
| 288 'not throw'); |
| 289 } catch (e) { |
| 290 assertEquals("Boo!!", e); |
| 291 } |
| 292 |
| 293 // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| 294 // whether [sync_iterator] is completed or not. |
| 295 assertEquals({ value: 'sync-value', done: false }, await iter.next('h')); |
| 296 assertEquals([ |
| 297 { |
| 298 method: 'return', |
| 299 sent: 'Boo!!', |
| 300 value: undefined, |
| 301 done: true |
| 302 }, |
| 303 { |
| 304 method: 'next', |
| 305 sent: 'h', |
| 306 value: 'sync-value', |
| 307 done: false |
| 308 } |
| 309 ], log); |
| 310 |
| 311 // If [sync_iterator].throw() does exist, return a Promise rejected with |
| 312 // the sent value. |
| 313 log = []; |
| 314 iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext, log)); |
| 315 try { |
| 316 await iter.throw('Boo!!'); |
| 317 assertUnreachable('Expected iter.throw(\'Boo!!\') to throw, but did not ' + |
| 318 'throw'); |
| 319 } catch (e) { |
| 320 assertEquals('Boo!!', e); |
| 321 } |
| 322 |
| 323 // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| 324 // whether [sync_iterator] is completed or not. |
| 325 assertEquals({ value: 'sync-value', done: false }, await iter.next('i')); |
| 326 assertEquals([ |
| 327 { |
| 328 method: 'next', |
| 329 sent: 'i', |
| 330 value: 'sync-value', |
| 331 done: false |
| 332 } |
| 333 ], log); |
| 334 |
| 335 log = []; |
| 336 iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kThrow, log)); |
| 337 try { |
| 338 await iter.throw('Boo!!'); |
| 339 assertUnreachable('Expected iter.throw(\'Boo!!\') to throw, but did not ' + |
| 340 'throw'); |
| 341 } catch (e) { |
| 342 assertEquals('Boo!!', e); |
| 343 } |
| 344 |
| 345 // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| 346 // whether [sync_iterator] is completed or not. |
| 347 assertEquals({ value: 'sync-value', done: false }, await iter.next('j')); |
| 348 assertEquals([ |
| 349 { |
| 350 method: 'throw', |
| 351 sent: 'Boo!!', |
| 352 value: undefined, |
| 353 done: false |
| 354 }, |
| 355 { |
| 356 method: 'next', |
| 357 sent: 'j', |
| 358 value: 'sync-value', |
| 359 done: false |
| 360 } |
| 361 ], log); |
| 362 |
| 363 // If [sync_iterator].throw() returns a resolved Promise or a Completion |
| 364 // with [[Type]] "normal" or "return", return a resolved Promise |
| 365 log = []; |
| 366 iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kThrowNormal, |
| 367 log)); |
| 368 assertEquals({ |
| 369 value: 'Boo!!', |
| 370 done: false |
| 371 }, await iter.throw('Boo!!')); |
| 372 |
| 373 // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| 374 // whether [sync_iterator] is completed or not. |
| 375 assertEquals({ value: 'sync-value', done: false }, await iter.next('k')); |
| 376 assertEquals([ |
| 377 { |
| 378 method: 'throw', |
| 379 sent: 'Boo!!', |
| 380 value: undefined, |
| 381 done: false |
| 382 }, |
| 383 { |
| 384 method: 'next', |
| 385 sent: 'k', |
| 386 value: 'sync-value', |
| 387 done: false |
| 388 } |
| 389 ], log); |
| 390 |
| 391 // Let nextValue be IteratorValue(nextResult). |
| 392 // IfAbruptRejectPromise(nextValue, promiseCapability).) |
| 393 iter = %CreateAsyncFromSyncIterator({ |
| 394 next() { return { get value() { throw "BadValue!" }, done: false }; } |
| 395 }); |
| 396 try { |
| 397 await iter.next(); |
| 398 assertUnreachable('Expected `iter.next()` to throw, but did not throw'); |
| 399 } catch (e) { |
| 400 assertEquals('BadValue!', e); |
| 401 } |
| 402 |
| 403 // Let nextDone be IteratorComplete(nextResult). |
| 404 // IfAbruptRejectPromise(nextDone, promiseCapability). |
| 405 iter = %CreateAsyncFromSyncIterator({ |
| 406 next() { return { value: undefined, get done() { throw "BadValue!" } }; } |
| 407 }); |
| 408 try { |
| 409 await iter.next(); |
| 410 assertUnreachable('Expected `iter.next()` to throw, but did not throw'); |
| 411 } catch (e) { |
| 412 assertEquals('BadValue!', e); |
| 413 } |
| 414 |
| 415 // IfAbruptRejectPromise(returnResult, promiseCapability). |
| 416 // Let returnValue be IteratorValue(returnResult). |
| 417 iter = %CreateAsyncFromSyncIterator({ |
| 418 return() { return { get value() { throw "BadValue!" }, done: false }; } |
| 419 }); |
| 420 try { |
| 421 await iter.return(); |
| 422 assertUnreachable('Expected `iter.return()` to throw, but did not throw'); |
| 423 } catch (e) { |
| 424 assertEquals('BadValue!', e); |
| 425 } |
| 426 |
| 427 // IfAbruptRejectPromise(returnValue, promiseCapability). |
| 428 // Let returnDone be IteratorComplete(returnResult). |
| 429 iter = %CreateAsyncFromSyncIterator({ |
| 430 return() { return { value: undefined, get done() { throw "BadValue!" } }; } |
| 431 }); |
| 432 try { |
| 433 await iter.return(); |
| 434 assertUnreachable('Expected `iter.return()` to throw, but did not throw'); |
| 435 } catch (e) { |
| 436 assertEquals('BadValue!', e); |
| 437 } |
| 438 |
| 439 // IfAbruptRejectPromise(throwResult, promiseCapability). |
| 440 // Let throwValue be IteratorValue(throwResult). |
| 441 iter = %CreateAsyncFromSyncIterator({ |
| 442 throw() { return { get value() { throw "BadValue!" }, done: false }; } |
| 443 }); |
| 444 try { |
| 445 await iter.throw(); |
| 446 assertUnreachable('Expected `iter.throw()` to throw, but did not throw'); |
| 447 } catch (e) { |
| 448 assertEquals('BadValue!', e); |
| 449 } |
| 450 |
| 451 // IfAbruptRejectPromise(throwValue, promiseCapability). |
| 452 // Let throwDone be IteratorComplete(throwResult). |
| 453 iter = %CreateAsyncFromSyncIterator({ |
| 454 throw() { return { value: undefined, get done() { throw "BadValue!" } }; } |
| 455 }); |
| 456 try { |
| 457 await iter.throw(); |
| 458 assertUnreachable('Expected `iter.throw()` to throw, but did not throw'); |
| 459 } catch (e) { |
| 460 assertEquals('BadValue!', e); |
| 461 } |
| 462 |
| 463 // Async-from-Sync iterator methods can be extracted via function.caller. |
| 464 // TODO(caitp): test extracted `throw` method using yield* in async generator. |
| 465 let extractor = [0, 1, 2, 3, 4,5,6,7,8,9]; |
| 466 let extractedNext; |
| 467 let extractedReturn; |
| 468 |
| 469 extractor[Symbol.iterator] = function() { |
| 470 let it = [][Symbol.iterator].call(extractor); |
| 471 let origNext = it.next, origThrow = it.throw, origReturn = it.return; |
| 472 function extractNext() { |
| 473 extractedNext = extractNext.caller; |
| 474 return origNext; |
| 475 } |
| 476 function extractReturn() { |
| 477 extractedReturn = extractReturn.caller; |
| 478 return origReturn; |
| 479 } |
| 480 Object.defineProperties(it, { |
| 481 "next": { get: extractNext, configurable: true }, |
| 482 "return": { get: extractReturn, configurable: true } |
| 483 }); |
| 484 return it; |
| 485 }; |
| 486 |
| 487 async function f() { |
| 488 let i; |
| 489 let it = extractor[Symbol.iterator](); |
| 490 for await (let x of it) break; |
| 491 for await (let x of it) return "x"; |
| 492 } |
| 493 await f(); |
| 494 |
| 495 assertEquals(typeof extractedNext, "function"); |
| 496 try { |
| 497 await extractedNext.call(undefined); |
| 498 throw new MjsUnitAssertionError( |
| 499 "Expected [Async-from-Sync Iterator].next.call(undefined) to throw " + |
| 500 "a TypeError, but did not throw."); |
| 501 } |
| 502 catch (e) { |
| 503 if (e.constructor !== TypeError) { |
| 504 throw new MjsUnitAssertionError( |
| 505 "Expected [Async-from-Sync Iterator].next.call(undefined) to throw " + |
| 506 "a TypeError, but threw '" + e + "'"); |
| 507 } |
| 508 } |
| 509 try { |
| 510 await extractedNext.call(1); |
| 511 throw new MjsUnitAssertionError( |
| 512 "Expected [Async-from-Sync Iterator].next.call(1) to throw " + |
| 513 "a TypeError, but did not throw."); |
| 514 } |
| 515 catch (e) { |
| 516 if (e.constructor !== TypeError) { |
| 517 throw new MjsUnitAssertionError( |
| 518 "Expected [Async-from-Sync Iterator].next.call(1) to throw " + |
| 519 "a TypeError, but threw '" + e + "'"); |
| 520 } |
| 521 } |
| 522 |
| 523 assertEquals(typeof extractedReturn, "function"); |
| 524 try { |
| 525 await extractedReturn.call(undefined); |
| 526 throw new MjsUnitAssertionError( |
| 527 "Expected [Async-from-Sync Iterator].return.call(undefined) to throw " + |
| 528 "a TypeError, but did not throw."); |
| 529 } |
| 530 catch (e) { |
| 531 if (e.constructor !== TypeError) { |
| 532 throw new MjsUnitAssertionError( |
| 533 "Expected [Async-from-Sync Iterator].return.call(undefined) to " + |
| 534 "throw a TypeError, but threw '" + e + "'"); |
| 535 } |
| 536 } |
| 537 try { |
| 538 await extractedReturn.call(1); |
| 539 throw new MjsUnitAssertionError( |
| 540 "Expected [Async-from-Sync Iterator].return.call(1) to throw " + |
| 541 "a TypeError, but did not throw."); |
| 542 } |
| 543 catch (e) { |
| 544 if (e.constructor !== TypeError) { |
| 545 throw new MjsUnitAssertionError( |
| 546 "Expected [Async-from-Sync Iterator].return.call(1) to throw " + |
| 547 "a TypeError, but threw '" + e + "'"); |
| 548 } |
| 549 } |
| 550 })().catch(function(error) { |
| 551 testFailed = true; |
| 552 testFailure = error; |
| 553 }); |
| 554 |
| 555 %RunMicrotasks(); |
| 556 |
| 557 if (testFailed) { |
| 558 throw testFailure; |
| 559 } |
OLD | NEW |