├── .github └── workflows │ └── bench.yml ├── .gitignore ├── LICENSE ├── README.md ├── bench.sh ├── loop.ts └── package.json /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | on: 2 | ## Allow triggering this workflow manually via GitHub CLI/web 3 | workflow_dispatch: 4 | 5 | jobs: 6 | bench: 7 | name: Run benchmarks 8 | timeout-minutes: 10 9 | runs-on: ubuntu-latest 10 | env: 11 | NODE_ENV: development 12 | strategy: 13 | matrix: 14 | node: [ '14', '16', 'lts/*' ] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: 'Use Node.js ${{ matrix.node }}' 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node }} 21 | - run: npm install 22 | - name: Run bench 23 | run: ./bench.sh 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | loop.js 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matteo Collina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-async-await-target-cost 2 | 3 | This project aims to benchmark the cost of an await in TypeScript with different targets. 4 | 5 | __TL;DR set at least es2017 as a target.__ 6 | 7 | ## The file 8 | 9 | ```typescript 10 | async function empty () { 11 | return true 12 | } 13 | 14 | async function loop () { 15 | console.time('loop') 16 | for (let i = 0; i < 1000000; i++) { 17 | await empty 18 | } 19 | console.timeEnd('loop') 20 | } 21 | 22 | loop() 23 | ``` 24 | 25 | ## The Results 26 | 27 | ``` 28 | benchmarking async/await with TS default target es3 29 | loop: 144.375ms 30 | ------------------------------------------------------ 31 | benchmarking async/await with TS target es2015 32 | loop: 153.384ms 33 | ------------------------------------------------------ 34 | benchmarking async/await with TS target es2016 35 | loop: 111.933ms 36 | ------------------------------------------------------ 37 | benchmarking async/await with TS target es2017 38 | loop: 96.598ms 39 | ------------------------------------------------------ 40 | benchmarking async/await with TS target es2018 41 | loop: 97.948ms 42 | ------------------------------------------------------ 43 | benchmarking async/await with TS target esnext 44 | loop: 97.288ms 45 | ``` 46 | 47 | ## Why 48 | 49 | If we are not setting the target in TypeScript, the code is compiled in es3. 50 | Therefore the compiler uses the following to run each promise: 51 | 52 | ```javascript 53 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 54 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 55 | return new (P || (P = Promise))(function (resolve, reject) { 56 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 57 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 58 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 59 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 60 | }); 61 | }; 62 | var __generator = (this && this.__generator) || function (thisArg, body) { 63 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 64 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 65 | function verb(n) { return function (v) { return step([n, v]); }; } 66 | function step(op) { 67 | if (f) throw new TypeError("Generator is already executing."); 68 | while (_) try { 69 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 70 | if (y = 0, t) op = [op[0] & 2, t.value]; 71 | switch (op[0]) { 72 | case 0: case 1: t = op; break; 73 | case 4: _.label++; return { value: op[1], done: false }; 74 | case 5: _.label++; y = op[1]; op = [0]; continue; 75 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 76 | default: 77 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 78 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 79 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 80 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 81 | if (t[2]) _.ops.pop(); 82 | _.trys.pop(); continue; 83 | } 84 | op = body.call(thisArg, _); 85 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 86 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 87 | } 88 | }; 89 | function empty() { 90 | return __awaiter(this, void 0, void 0, function () { 91 | return __generator(this, function (_a) { 92 | return [2 /*return*/, true]; 93 | }); 94 | }); 95 | } 96 | function loop() { 97 | return __awaiter(this, void 0, void 0, function () { 98 | var i; 99 | return __generator(this, function (_a) { 100 | switch (_a.label) { 101 | case 0: 102 | console.time('loop'); 103 | i = 0; 104 | _a.label = 1; 105 | case 1: 106 | if (!(i < 1000000)) return [3 /*break*/, 4]; 107 | return [4 /*yield*/, empty]; 108 | case 2: 109 | _a.sent(); 110 | _a.label = 3; 111 | case 3: 112 | i++; 113 | return [3 /*break*/, 1]; 114 | case 4: 115 | console.timeEnd('loop'); 116 | return [2 /*return*/]; 117 | } 118 | }); 119 | }); 120 | } 121 | loop(); 122 | ``` 123 | 124 | As you can imagine the code is significantly more expensive to run than the internal implementation of V8. 125 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "benchmarking async/await with TS default target es3" 6 | npx tsc --lib es2015,dom loop.ts --outfile loop.es2015-dom.js 7 | node loop.es2015-dom.js 8 | 9 | echo "------------------------------------------------------" 10 | 11 | echo "benchmarking async/await with TS target es2015" 12 | npx tsc --target es2015 loop.ts --outfile loop.es2015.js 13 | node loop.es2015.js 14 | 15 | echo "------------------------------------------------------" 16 | 17 | echo "benchmarking async/await with TS target es2016" 18 | npx tsc --target es2016 loop.ts --outfile loop.es2016.js 19 | node loop.es2016.js 20 | 21 | echo "------------------------------------------------------" 22 | 23 | echo "benchmarking async/await with TS target es2017" 24 | npx tsc --target es2017 loop.ts --outfile loop.es2017.js 25 | node loop.es2017.js 26 | 27 | echo "------------------------------------------------------" 28 | 29 | echo "benchmarking async/await with TS target es2018" 30 | npx tsc --target es2018 loop.ts --outfile loop.es2018.js 31 | node loop.es2018.js 32 | 33 | echo "------------------------------------------------------" 34 | 35 | echo "benchmarking async/await with TS target es2022" 36 | npx tsc --target es2022 loop.ts --outfile loop.es2022.js 37 | node loop.es2022.js 38 | 39 | 40 | echo "------------------------------------------------------" 41 | 42 | echo "benchmarking async/await with TS target esnext" 43 | npx tsc --target esnext loop.ts --outfile loop.esnext.js 44 | node loop.esnext.js 45 | -------------------------------------------------------------------------------- /loop.ts: -------------------------------------------------------------------------------- 1 | 2 | async function empty () { 3 | return true 4 | } 5 | 6 | async function loop () { 7 | console.time('loop') 8 | for (let i = 0; i < 1000000; i++) { 9 | await empty 10 | } 11 | console.timeEnd('loop') 12 | } 13 | 14 | loop() 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-async-await-target-cost", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mcollina/typescript-async-await-target-cost.git" 12 | }, 13 | "author": "Matteo Collina ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/mcollina/typescript-async-await-target-cost/issues" 17 | }, 18 | "homepage": "https://github.com/mcollina/typescript-async-await-target-cost#readme", 19 | "dependencies": { 20 | "typescript": "^4.6.2" 21 | } 22 | } 23 | --------------------------------------------------------------------------------