├── .gitignore ├── .npmignore ├── .travis.yml ├── contributing.md ├── dist ├── awaiting.common.js └── awaiting.umd.js ├── docs ├── api.html ├── assets │ ├── anchor.js │ ├── bass-addons.css │ ├── bass.css │ ├── fonts │ │ ├── EOT │ │ │ ├── SourceCodePro-Bold.eot │ │ │ └── SourceCodePro-Regular.eot │ │ ├── LICENSE.txt │ │ ├── OTF │ │ │ ├── SourceCodePro-Bold.otf │ │ │ └── SourceCodePro-Regular.otf │ │ ├── TTF │ │ │ ├── SourceCodePro-Bold.ttf │ │ │ └── SourceCodePro-Regular.ttf │ │ ├── WOFF │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff │ │ │ │ └── SourceCodePro-Regular.otf.woff │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff │ │ │ │ └── SourceCodePro-Regular.ttf.woff │ │ ├── WOFF2 │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff2 │ │ │ │ └── SourceCodePro-Regular.otf.woff2 │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff2 │ │ │ │ └── SourceCodePro-Regular.ttf.woff2 │ │ └── source-code-pro.css │ ├── github.css │ ├── site.js │ └── style.css ├── examples │ ├── awaiting.js │ ├── awaiting.umd.js │ └── kittens.html ├── index.html └── style.css ├── examples ├── awaiting.umd.js └── kittens.html ├── lib └── awaiting.js ├── package.json ├── readme.md ├── test ├── awaited.test.js ├── awaiting.test.js ├── browser │ ├── index.html │ └── test.js ├── callback.test.js ├── delay.test.js ├── errorlist.test.js ├── failure.test.js ├── fixtures │ ├── rejection-ignore.js │ ├── rejection-multiple.js │ ├── rejection-swallow.js │ └── rejection-throw.js ├── limit.test.js ├── list.test.js ├── map.test.js ├── node │ ├── event.test.js │ └── rejections.test.js ├── object.test.js ├── result.test.js ├── set.test.js ├── single.test.js ├── success.test.js └── time.test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.env 3 | .DS_Store 4 | .nyc_output 5 | coverage 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.env 3 | .DS_Store 4 | docs 5 | .nyc_output 6 | coverage 7 | examples 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7.6.0" 4 | - "node" 5 | script: yarn test 6 | after_success: yarn coverage 7 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Awaiting 2 | 3 | First, thanks for taking the time to contribute! 4 | 5 | Following this guide will help your bug report, suggestion, or pull request get fixed, implemented, or merged quickly and smoothly. 6 | 7 | ## Bug reports 8 | 9 | Bug reports are filed on [GitHub issues](https://github.com/hunterloftis/awaiting/issues). 10 | 11 | The best bug reports are Pull Requests with new test cases demonstrating the bug. If that's not an option, please provide: 12 | 13 | 1. The Awaiting, node, npm, yarn, and operating system versions you're using. 14 | 2. Short, simple reproduction code. 15 | 3. What you expect to happen. 16 | 4. What actually happens. 17 | 18 | ## Suggestions 19 | 20 | To suggest an enhancement, please open a 21 | [GitHub issue](https://github.com/hunterloftis/awaiting/issues) 22 | titled "Enhancement: your proposal title." 23 | 24 | The best suggestions include code examples of how your feature's 25 | API might look to a user (how will people use this enhancement?). 26 | 27 | Implementation recommendations are fine, 28 | but usually less useful at this early stage - 29 | it's more important to make sure the feature will 30 | provide a pleasant development experience, and then 31 | to sort out the implementation in a Pull Request. 32 | 33 | Other good information to include in a suggestion is how much of the implementation you're capable of / interested in implementing. 34 | 35 | ## Pull Requests 36 | 37 | In order to make the best use of your time, 38 | please start new feature requests as [suggestions](#suggestions) rather than PRs. 39 | That will allow us to discuss the feature and its API - and to settle on a nice design that aligns with project goals and scope - before anyone writes any code. 40 | 41 | Once we've decided that a feature would be useful, 42 | settled on an API, 43 | and discussed implementation details, 44 | your Pull Request will be easy to review and merge. 45 | 46 | Staying in the project's requirements is mostly automated. To start working on Awaiting: 47 | 48 | ``` 49 | $ git clone https://github.com/hunterloftis/awaiting.git 50 | $ cd awaiting 51 | $ yarn install 52 | ``` 53 | 54 | During development, the test script will ensure you're on spec: 55 | 56 | ``` 57 | $ yarn test # lints, runs tests, checks coverage % 58 | ``` 59 | 60 | Once you've implemented your fix or feature, 61 | ensure tests are working in the browser and you're at 100% test coverage: 62 | 63 | ``` 64 | $ yarn test:browser 65 | $ yarn coverage:report 66 | ``` 67 | 68 | Finally, document your code with JSDoc and submit a PR! 69 | -------------------------------------------------------------------------------- /dist/awaiting.common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | /** 6 | * The async/await utility for browsers and Node.js. 7 | * 8 | * ( 9 | * [examples](https://github.com/hunterloftis/awaiting#examples) - 10 | * [github](https://github.com/hunterloftis/awaiting) - 11 | * [npm](https://www.npmjs.com/package/awaiting) - 12 | * [suggestions / bug reports](https://github.com/hunterloftis/awaiting/issues) - 13 | * [installation](https://github.com/hunterloftis/awaiting#installation) - 14 | * [motivation](https://github.com/hunterloftis/awaiting#motivation) 15 | * ) 16 | * 17 | * **`$ yarn add awaiting`** 18 | * 19 | * **`$ npm install awaiting --save`** 20 | * 21 | * **` 1234 | 1235 | 1236 | 1237 | -------------------------------------------------------------------------------- /docs/assets/anchor.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AnchorJS - v1.2.1 - 2015-07-02 3 | * https://github.com/bryanbraun/anchorjs 4 | * Copyright (c) 2015 Bryan Braun; Licensed MIT 5 | */ 6 | 7 | function AnchorJS(options) { 8 | 'use strict'; 9 | 10 | this.options = options || {}; 11 | 12 | this._applyRemainingDefaultOptions = function(opts) { 13 | this.options.icon = this.options.hasOwnProperty('icon') ? opts.icon : '\ue9cb'; // Accepts characters (and also URLs?), like '#', '¶', '❡', or '§'. 14 | this.options.visible = this.options.hasOwnProperty('visible') ? opts.visible : 'hover'; // Also accepts 'always' 15 | this.options.placement = this.options.hasOwnProperty('placement') ? opts.placement : 'right'; // Also accepts 'left' 16 | this.options.class = this.options.hasOwnProperty('class') ? opts.class : ''; // Accepts any class name. 17 | }; 18 | 19 | this._applyRemainingDefaultOptions(options); 20 | 21 | this.add = function(selector) { 22 | var elements, 23 | elsWithIds, 24 | idList, 25 | elementID, 26 | i, 27 | roughText, 28 | tidyText, 29 | index, 30 | count, 31 | newTidyText, 32 | readableID, 33 | anchor; 34 | 35 | this._applyRemainingDefaultOptions(this.options); 36 | 37 | // Provide a sensible default selector, if none is given. 38 | if (!selector) { 39 | selector = 'h1, h2, h3, h4, h5, h6'; 40 | } else if (typeof selector !== 'string') { 41 | throw new Error('The selector provided to AnchorJS was invalid.'); 42 | } 43 | 44 | elements = document.querySelectorAll(selector); 45 | if (elements.length === 0) { 46 | return false; 47 | } 48 | 49 | this._addBaselineStyles(); 50 | 51 | // We produce a list of existing IDs so we don't generate a duplicate. 52 | elsWithIds = document.querySelectorAll('[id]'); 53 | idList = [].map.call(elsWithIds, function assign(el) { 54 | return el.id; 55 | }); 56 | 57 | for (i = 0; i < elements.length; i++) { 58 | 59 | if (elements[i].hasAttribute('id')) { 60 | elementID = elements[i].getAttribute('id'); 61 | } else { 62 | roughText = elements[i].textContent; 63 | 64 | // Refine it so it makes a good ID. Strip out non-safe characters, replace 65 | // spaces with hyphens, truncate to 32 characters, and make toLowerCase. 66 | // 67 | // Example string: // '⚡⚡⚡ Unicode icons are cool--but they definitely don't belong in a URL fragment.' 68 | tidyText = roughText.replace(/[^\w\s-]/gi, '') // ' Unicode icons are cool--but they definitely dont belong in a URL fragment' 69 | .replace(/\s+/g, '-') // '-Unicode-icons-are-cool--but-they-definitely-dont-belong-in-a-URL-fragment' 70 | .replace(/-{2,}/g, '-') // '-Unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-URL-fragment' 71 | .substring(0, 64) // '-Unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-URL' 72 | .replace(/^-+|-+$/gm, '') // 'Unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-URL' 73 | .toLowerCase(); // 'unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-url' 74 | 75 | // Compare our generated ID to existing IDs (and increment it if needed) 76 | // before we add it to the page. 77 | newTidyText = tidyText; 78 | count = 0; 79 | do { 80 | if (index !== undefined) { 81 | newTidyText = tidyText + '-' + count; 82 | } 83 | // .indexOf is supported in IE9+. 84 | index = idList.indexOf(newTidyText); 85 | count += 1; 86 | } while (index !== -1); 87 | index = undefined; 88 | idList.push(newTidyText); 89 | 90 | // Assign it to our element. 91 | // Currently the setAttribute element is only supported in IE9 and above. 92 | elements[i].setAttribute('id', newTidyText); 93 | 94 | elementID = newTidyText; 95 | } 96 | 97 | readableID = elementID.replace(/-/g, ' '); 98 | 99 | // The following code builds the following DOM structure in a more effiecient (albeit opaque) way. 100 | // ''; 101 | anchor = document.createElement('a'); 102 | anchor.className = 'anchorjs-link ' + this.options.class; 103 | anchor.href = '#' + elementID; 104 | anchor.setAttribute('aria-label', 'Anchor link for: ' + readableID); 105 | anchor.setAttribute('data-anchorjs-icon', this.options.icon); 106 | 107 | if (this.options.visible === 'always') { 108 | anchor.style.opacity = '1'; 109 | } 110 | 111 | if (this.options.icon === '\ue9cb') { 112 | anchor.style.fontFamily = 'anchorjs-icons'; 113 | anchor.style.fontStyle = 'normal'; 114 | anchor.style.fontVariant = 'normal'; 115 | anchor.style.fontWeight = 'normal'; 116 | anchor.style.lineHeight = 1; 117 | } 118 | 119 | if (this.options.placement === 'left') { 120 | anchor.style.position = 'absolute'; 121 | anchor.style.marginLeft = '-1em'; 122 | anchor.style.paddingRight = '0.5em'; 123 | elements[i].insertBefore(anchor, elements[i].firstChild); 124 | } else { // if the option provided is `right` (or anything else). 125 | anchor.style.paddingLeft = '0.375em'; 126 | elements[i].appendChild(anchor); 127 | } 128 | } 129 | 130 | return this; 131 | }; 132 | 133 | this.remove = function(selector) { 134 | var domAnchor, 135 | elements = document.querySelectorAll(selector); 136 | for (var i = 0; i < elements.length; i++) { 137 | domAnchor = elements[i].querySelector('.anchorjs-link'); 138 | if (domAnchor) { 139 | elements[i].removeChild(domAnchor); 140 | } 141 | } 142 | return this; 143 | }; 144 | 145 | this._addBaselineStyles = function() { 146 | // We don't want to add global baseline styles if they've been added before. 147 | if (document.head.querySelector('style.anchorjs') !== null) { 148 | return; 149 | } 150 | 151 | var style = document.createElement('style'), 152 | linkRule = 153 | ' .anchorjs-link {' + 154 | ' opacity: 0;' + 155 | ' text-decoration: none;' + 156 | ' -webkit-font-smoothing: antialiased;' + 157 | ' -moz-osx-font-smoothing: grayscale;' + 158 | ' }', 159 | hoverRule = 160 | ' *:hover > .anchorjs-link,' + 161 | ' .anchorjs-link:focus {' + 162 | ' opacity: 1;' + 163 | ' }', 164 | anchorjsLinkFontFace = 165 | ' @font-face {' + 166 | ' font-family: "anchorjs-icons";' + 167 | ' font-style: normal;' + 168 | ' font-weight: normal;' + // Icon from icomoon; 10px wide & 10px tall; 2 empty below & 4 above 169 | ' src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBTUAAAC8AAAAYGNtYXAWi9QdAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zgq29TcAAAF4AAABNGhlYWQEZM3pAAACrAAAADZoaGVhBhUDxgAAAuQAAAAkaG10eASAADEAAAMIAAAAFGxvY2EAKACuAAADHAAAAAxtYXhwAAgAVwAAAygAAAAgbmFtZQ5yJ3cAAANIAAAB2nBvc3QAAwAAAAAFJAAAACAAAwJAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpywPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6cv//f//AAAAAAAg6cv//f//AAH/4xY5AAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAACADEARAJTAsAAKwBUAAABIiYnJjQ/AT4BMzIWFxYUDwEGIicmND8BNjQnLgEjIgYPAQYUFxYUBw4BIwciJicmND8BNjIXFhQPAQYUFx4BMzI2PwE2NCcmNDc2MhcWFA8BDgEjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAEAAAABAACiToc1Xw889QALBAAAAAAA0XnFFgAAAADRecUWAAAAAAJTAsAAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAAlMAAQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAACAAAAAoAAMQAAAAAACgAUAB4AmgABAAAABQBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIABwCfAAEAAAAAAAMADgBLAAEAAAAAAAQADgC0AAEAAAAAAAUACwAqAAEAAAAAAAYADgB1AAEAAAAAAAoAGgDeAAMAAQQJAAEAHAAOAAMAAQQJAAIADgCmAAMAAQQJAAMAHABZAAMAAQQJAAQAHADCAAMAAQQJAAUAFgA1AAMAAQQJAAYAHACDAAMAAQQJAAoANAD4YW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwYW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzYW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzUmVndWxhcgBSAGUAZwB1AGwAYQByYW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format("truetype");' + 170 | ' }', 171 | pseudoElContent = 172 | ' [data-anchorjs-icon]::after {' + 173 | ' content: attr(data-anchorjs-icon);' + 174 | ' }', 175 | firstStyleEl; 176 | 177 | style.className = 'anchorjs'; 178 | style.appendChild(document.createTextNode('')); // Necessary for Webkit. 179 | 180 | // We place it in the head with the other style tags, if possible, so as to 181 | // not look out of place. We insert before the others so these styles can be 182 | // overridden if necessary. 183 | firstStyleEl = document.head.querySelector('[rel="stylesheet"], style'); 184 | if (firstStyleEl === undefined) { 185 | document.head.appendChild(style); 186 | } else { 187 | document.head.insertBefore(style, firstStyleEl); 188 | } 189 | 190 | style.sheet.insertRule(linkRule, style.sheet.cssRules.length); 191 | style.sheet.insertRule(hoverRule, style.sheet.cssRules.length); 192 | style.sheet.insertRule(pseudoElContent, style.sheet.cssRules.length); 193 | style.sheet.insertRule(anchorjsLinkFontFace, style.sheet.cssRules.length); 194 | }; 195 | } 196 | 197 | var anchors = new AnchorJS(); 198 | -------------------------------------------------------------------------------- /docs/assets/bass-addons.css: -------------------------------------------------------------------------------- 1 | .input { 2 | font-family: inherit; 3 | display: block; 4 | width: 100%; 5 | height: 2rem; 6 | padding: .5rem; 7 | margin-bottom: 1rem; 8 | border: 1px solid #ccc; 9 | font-size: .875rem; 10 | border-radius: 3px; 11 | box-sizing: border-box; 12 | } 13 | -------------------------------------------------------------------------------- /docs/assets/bass.css: -------------------------------------------------------------------------------- 1 | /*! Basscss | http://basscss.com | MIT License */ 2 | 3 | .h1{ font-size: 2rem } 4 | .h2{ font-size: 1.5rem } 5 | .h3{ font-size: 1.25rem } 6 | .h4{ font-size: 1rem } 7 | .h5{ font-size: .875rem } 8 | .h6{ font-size: .75rem } 9 | 10 | .font-family-inherit{ font-family:inherit } 11 | .font-size-inherit{ font-size:inherit } 12 | .text-decoration-none{ text-decoration:none } 13 | 14 | .bold{ font-weight: bold; font-weight: bold } 15 | .regular{ font-weight:normal } 16 | .italic{ font-style:italic } 17 | .caps{ text-transform:uppercase; letter-spacing: .2em; } 18 | 19 | .left-align{ text-align:left } 20 | .center{ text-align:center } 21 | .right-align{ text-align:right } 22 | .justify{ text-align:justify } 23 | 24 | .nowrap{ white-space:nowrap } 25 | .break-word{ word-wrap:break-word } 26 | 27 | .line-height-1{ line-height: 1 } 28 | .line-height-2{ line-height: 1.125 } 29 | .line-height-3{ line-height: 1.25 } 30 | .line-height-4{ line-height: 1.5 } 31 | 32 | .list-style-none{ list-style:none } 33 | .underline{ text-decoration:underline } 34 | 35 | .truncate{ 36 | max-width:100%; 37 | overflow:hidden; 38 | text-overflow:ellipsis; 39 | white-space:nowrap; 40 | } 41 | 42 | .list-reset{ 43 | list-style:none; 44 | padding-left:0; 45 | } 46 | 47 | .inline{ display:inline } 48 | .block{ display:block } 49 | .inline-block{ display:inline-block } 50 | .table{ display:table } 51 | .table-cell{ display:table-cell } 52 | 53 | .overflow-hidden{ overflow:hidden } 54 | .overflow-scroll{ overflow:scroll } 55 | .overflow-auto{ overflow:auto } 56 | 57 | .clearfix:before, 58 | .clearfix:after{ 59 | content:" "; 60 | display:table 61 | } 62 | .clearfix:after{ clear:both } 63 | 64 | .left{ float:left } 65 | .right{ float:right } 66 | 67 | .fit{ max-width:100% } 68 | 69 | .max-width-1{ max-width: 24rem } 70 | .max-width-2{ max-width: 32rem } 71 | .max-width-3{ max-width: 48rem } 72 | .max-width-4{ max-width: 64rem } 73 | 74 | .border-box{ box-sizing:border-box } 75 | 76 | .align-baseline{ vertical-align:baseline } 77 | .align-top{ vertical-align:top } 78 | .align-middle{ vertical-align:middle } 79 | .align-bottom{ vertical-align:bottom } 80 | 81 | .m0{ margin:0 } 82 | .mt0{ margin-top:0 } 83 | .mr0{ margin-right:0 } 84 | .mb0{ margin-bottom:0 } 85 | .ml0{ margin-left:0 } 86 | .mx0{ margin-left:0; margin-right:0 } 87 | .my0{ margin-top:0; margin-bottom:0 } 88 | 89 | .m1{ margin: .5rem } 90 | .mt1{ margin-top: .5rem } 91 | .mr1{ margin-right: .5rem } 92 | .mb1{ margin-bottom: .5rem } 93 | .ml1{ margin-left: .5rem } 94 | .mx1{ margin-left: .5rem; margin-right: .5rem } 95 | .my1{ margin-top: .5rem; margin-bottom: .5rem } 96 | 97 | .m2{ margin: 1rem } 98 | .mt2{ margin-top: 1rem } 99 | .mr2{ margin-right: 1rem } 100 | .mb2{ margin-bottom: 1rem } 101 | .ml2{ margin-left: 1rem } 102 | .mx2{ margin-left: 1rem; margin-right: 1rem } 103 | .my2{ margin-top: 1rem; margin-bottom: 1rem } 104 | 105 | .m3{ margin: 2rem } 106 | .mt3{ margin-top: 2rem } 107 | .mr3{ margin-right: 2rem } 108 | .mb3{ margin-bottom: 2rem } 109 | .ml3{ margin-left: 2rem } 110 | .mx3{ margin-left: 2rem; margin-right: 2rem } 111 | .my3{ margin-top: 2rem; margin-bottom: 2rem } 112 | 113 | .m4{ margin: 4rem } 114 | .mt4{ margin-top: 4rem } 115 | .mr4{ margin-right: 4rem } 116 | .mb4{ margin-bottom: 4rem } 117 | .ml4{ margin-left: 4rem } 118 | .mx4{ margin-left: 4rem; margin-right: 4rem } 119 | .my4{ margin-top: 4rem; margin-bottom: 4rem } 120 | 121 | .mxn1{ margin-left: -.5rem; margin-right: -.5rem; } 122 | .mxn2{ margin-left: -1rem; margin-right: -1rem; } 123 | .mxn3{ margin-left: -2rem; margin-right: -2rem; } 124 | .mxn4{ margin-left: -4rem; margin-right: -4rem; } 125 | 126 | .ml-auto{ margin-left:auto } 127 | .mr-auto{ margin-right:auto } 128 | .mx-auto{ margin-left:auto; margin-right:auto; } 129 | 130 | .p0{ padding:0 } 131 | .pt0{ padding-top:0 } 132 | .pr0{ padding-right:0 } 133 | .pb0{ padding-bottom:0 } 134 | .pl0{ padding-left:0 } 135 | .px0{ padding-left:0; padding-right:0 } 136 | .py0{ padding-top:0; padding-bottom:0 } 137 | 138 | .p1{ padding: .5rem } 139 | .pt1{ padding-top: .5rem } 140 | .pr1{ padding-right: .5rem } 141 | .pb1{ padding-bottom: .5rem } 142 | .pl1{ padding-left: .5rem } 143 | .py1{ padding-top: .5rem; padding-bottom: .5rem } 144 | .px1{ padding-left: .5rem; padding-right: .5rem } 145 | 146 | .p2{ padding: 1rem } 147 | .pt2{ padding-top: 1rem } 148 | .pr2{ padding-right: 1rem } 149 | .pb2{ padding-bottom: 1rem } 150 | .pl2{ padding-left: 1rem } 151 | .py2{ padding-top: 1rem; padding-bottom: 1rem } 152 | .px2{ padding-left: 1rem; padding-right: 1rem } 153 | 154 | .p3{ padding: 2rem } 155 | .pt3{ padding-top: 2rem } 156 | .pr3{ padding-right: 2rem } 157 | .pb3{ padding-bottom: 2rem } 158 | .pl3{ padding-left: 2rem } 159 | .py3{ padding-top: 2rem; padding-bottom: 2rem } 160 | .px3{ padding-left: 2rem; padding-right: 2rem } 161 | 162 | .p4{ padding: 4rem } 163 | .pt4{ padding-top: 4rem } 164 | .pr4{ padding-right: 4rem } 165 | .pb4{ padding-bottom: 4rem } 166 | .pl4{ padding-left: 4rem } 167 | .py4{ padding-top: 4rem; padding-bottom: 4rem } 168 | .px4{ padding-left: 4rem; padding-right: 4rem } 169 | 170 | .col{ 171 | float:left; 172 | box-sizing:border-box; 173 | } 174 | 175 | .col-right{ 176 | float:right; 177 | box-sizing:border-box; 178 | } 179 | 180 | .col-1{ 181 | width:8.33333%; 182 | } 183 | 184 | .col-2{ 185 | width:16.66667%; 186 | } 187 | 188 | .col-3{ 189 | width:25%; 190 | } 191 | 192 | .col-4{ 193 | width:33.33333%; 194 | } 195 | 196 | .col-5{ 197 | width:41.66667%; 198 | } 199 | 200 | .col-6{ 201 | width:50%; 202 | } 203 | 204 | .col-7{ 205 | width:58.33333%; 206 | } 207 | 208 | .col-8{ 209 | width:66.66667%; 210 | } 211 | 212 | .col-9{ 213 | width:75%; 214 | } 215 | 216 | .col-10{ 217 | width:83.33333%; 218 | } 219 | 220 | .col-11{ 221 | width:91.66667%; 222 | } 223 | 224 | .col-12{ 225 | width:100%; 226 | } 227 | @media (min-width: 40em){ 228 | 229 | .sm-col{ 230 | float:left; 231 | box-sizing:border-box; 232 | } 233 | 234 | .sm-col-right{ 235 | float:right; 236 | box-sizing:border-box; 237 | } 238 | 239 | .sm-col-1{ 240 | width:8.33333%; 241 | } 242 | 243 | .sm-col-2{ 244 | width:16.66667%; 245 | } 246 | 247 | .sm-col-3{ 248 | width:25%; 249 | } 250 | 251 | .sm-col-4{ 252 | width:33.33333%; 253 | } 254 | 255 | .sm-col-5{ 256 | width:41.66667%; 257 | } 258 | 259 | .sm-col-6{ 260 | width:50%; 261 | } 262 | 263 | .sm-col-7{ 264 | width:58.33333%; 265 | } 266 | 267 | .sm-col-8{ 268 | width:66.66667%; 269 | } 270 | 271 | .sm-col-9{ 272 | width:75%; 273 | } 274 | 275 | .sm-col-10{ 276 | width:83.33333%; 277 | } 278 | 279 | .sm-col-11{ 280 | width:91.66667%; 281 | } 282 | 283 | .sm-col-12{ 284 | width:100%; 285 | } 286 | 287 | } 288 | @media (min-width: 52em){ 289 | 290 | .md-col{ 291 | float:left; 292 | box-sizing:border-box; 293 | } 294 | 295 | .md-col-right{ 296 | float:right; 297 | box-sizing:border-box; 298 | } 299 | 300 | .md-col-1{ 301 | width:8.33333%; 302 | } 303 | 304 | .md-col-2{ 305 | width:16.66667%; 306 | } 307 | 308 | .md-col-3{ 309 | width:25%; 310 | } 311 | 312 | .md-col-4{ 313 | width:33.33333%; 314 | } 315 | 316 | .md-col-5{ 317 | width:41.66667%; 318 | } 319 | 320 | .md-col-6{ 321 | width:50%; 322 | } 323 | 324 | .md-col-7{ 325 | width:58.33333%; 326 | } 327 | 328 | .md-col-8{ 329 | width:66.66667%; 330 | } 331 | 332 | .md-col-9{ 333 | width:75%; 334 | } 335 | 336 | .md-col-10{ 337 | width:83.33333%; 338 | } 339 | 340 | .md-col-11{ 341 | width:91.66667%; 342 | } 343 | 344 | .md-col-12{ 345 | width:100%; 346 | } 347 | 348 | } 349 | @media (min-width: 64em){ 350 | 351 | .lg-col{ 352 | float:left; 353 | box-sizing:border-box; 354 | } 355 | 356 | .lg-col-right{ 357 | float:right; 358 | box-sizing:border-box; 359 | } 360 | 361 | .lg-col-1{ 362 | width:8.33333%; 363 | } 364 | 365 | .lg-col-2{ 366 | width:16.66667%; 367 | } 368 | 369 | .lg-col-3{ 370 | width:25%; 371 | } 372 | 373 | .lg-col-4{ 374 | width:33.33333%; 375 | } 376 | 377 | .lg-col-5{ 378 | width:41.66667%; 379 | } 380 | 381 | .lg-col-6{ 382 | width:50%; 383 | } 384 | 385 | .lg-col-7{ 386 | width:58.33333%; 387 | } 388 | 389 | .lg-col-8{ 390 | width:66.66667%; 391 | } 392 | 393 | .lg-col-9{ 394 | width:75%; 395 | } 396 | 397 | .lg-col-10{ 398 | width:83.33333%; 399 | } 400 | 401 | .lg-col-11{ 402 | width:91.66667%; 403 | } 404 | 405 | .lg-col-12{ 406 | width:100%; 407 | } 408 | 409 | } 410 | .flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 411 | 412 | @media (min-width: 40em){ 413 | .sm-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 414 | } 415 | 416 | @media (min-width: 52em){ 417 | .md-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 418 | } 419 | 420 | @media (min-width: 64em){ 421 | .lg-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 422 | } 423 | 424 | .flex-column{ -webkit-box-orient:vertical; -webkit-box-direction:normal; -webkit-flex-direction:column; -ms-flex-direction:column; flex-direction:column } 425 | .flex-wrap{ -webkit-flex-wrap:wrap; -ms-flex-wrap:wrap; flex-wrap:wrap } 426 | 427 | .items-start{ -webkit-box-align:start; -webkit-align-items:flex-start; -ms-flex-align:start; -ms-grid-row-align:flex-start; align-items:flex-start } 428 | .items-end{ -webkit-box-align:end; -webkit-align-items:flex-end; -ms-flex-align:end; -ms-grid-row-align:flex-end; align-items:flex-end } 429 | .items-center{ -webkit-box-align:center; -webkit-align-items:center; -ms-flex-align:center; -ms-grid-row-align:center; align-items:center } 430 | .items-baseline{ -webkit-box-align:baseline; -webkit-align-items:baseline; -ms-flex-align:baseline; -ms-grid-row-align:baseline; align-items:baseline } 431 | .items-stretch{ -webkit-box-align:stretch; -webkit-align-items:stretch; -ms-flex-align:stretch; -ms-grid-row-align:stretch; align-items:stretch } 432 | 433 | .self-start{ -webkit-align-self:flex-start; -ms-flex-item-align:start; align-self:flex-start } 434 | .self-end{ -webkit-align-self:flex-end; -ms-flex-item-align:end; align-self:flex-end } 435 | .self-center{ -webkit-align-self:center; -ms-flex-item-align:center; align-self:center } 436 | .self-baseline{ -webkit-align-self:baseline; -ms-flex-item-align:baseline; align-self:baseline } 437 | .self-stretch{ -webkit-align-self:stretch; -ms-flex-item-align:stretch; align-self:stretch } 438 | 439 | .justify-start{ -webkit-box-pack:start; -webkit-justify-content:flex-start; -ms-flex-pack:start; justify-content:flex-start } 440 | .justify-end{ -webkit-box-pack:end; -webkit-justify-content:flex-end; -ms-flex-pack:end; justify-content:flex-end } 441 | .justify-center{ -webkit-box-pack:center; -webkit-justify-content:center; -ms-flex-pack:center; justify-content:center } 442 | .justify-between{ -webkit-box-pack:justify; -webkit-justify-content:space-between; -ms-flex-pack:justify; justify-content:space-between } 443 | .justify-around{ -webkit-justify-content:space-around; -ms-flex-pack:distribute; justify-content:space-around } 444 | 445 | .content-start{ -webkit-align-content:flex-start; -ms-flex-line-pack:start; align-content:flex-start } 446 | .content-end{ -webkit-align-content:flex-end; -ms-flex-line-pack:end; align-content:flex-end } 447 | .content-center{ -webkit-align-content:center; -ms-flex-line-pack:center; align-content:center } 448 | .content-between{ -webkit-align-content:space-between; -ms-flex-line-pack:justify; align-content:space-between } 449 | .content-around{ -webkit-align-content:space-around; -ms-flex-line-pack:distribute; align-content:space-around } 450 | .content-stretch{ -webkit-align-content:stretch; -ms-flex-line-pack:stretch; align-content:stretch } 451 | .flex-auto{ 452 | -webkit-box-flex:1; 453 | -webkit-flex:1 1 auto; 454 | -ms-flex:1 1 auto; 455 | flex:1 1 auto; 456 | min-width:0; 457 | min-height:0; 458 | } 459 | .flex-none{ -webkit-box-flex:0; -webkit-flex:none; -ms-flex:none; flex:none } 460 | 461 | .order-0{ -webkit-box-ordinal-group:1; -webkit-order:0; -ms-flex-order:0; order:0 } 462 | .order-1{ -webkit-box-ordinal-group:2; -webkit-order:1; -ms-flex-order:1; order:1 } 463 | .order-2{ -webkit-box-ordinal-group:3; -webkit-order:2; -ms-flex-order:2; order:2 } 464 | .order-3{ -webkit-box-ordinal-group:4; -webkit-order:3; -ms-flex-order:3; order:3 } 465 | .order-last{ -webkit-box-ordinal-group:100000; -webkit-order:99999; -ms-flex-order:99999; order:99999 } 466 | 467 | .relative{ position:relative } 468 | .absolute{ position:absolute } 469 | .fixed{ position:fixed } 470 | 471 | .top-0{ top:0 } 472 | .right-0{ right:0 } 473 | .bottom-0{ bottom:0 } 474 | .left-0{ left:0 } 475 | 476 | .z1{ z-index: 1 } 477 | .z2{ z-index: 2 } 478 | .z3{ z-index: 3 } 479 | .z4{ z-index: 4 } 480 | 481 | .border{ 482 | border-style:solid; 483 | border-width: 1px; 484 | } 485 | 486 | .border-top{ 487 | border-top-style:solid; 488 | border-top-width: 1px; 489 | } 490 | 491 | .border-right{ 492 | border-right-style:solid; 493 | border-right-width: 1px; 494 | } 495 | 496 | .border-bottom{ 497 | border-bottom-style:solid; 498 | border-bottom-width: 1px; 499 | } 500 | 501 | .border-left{ 502 | border-left-style:solid; 503 | border-left-width: 1px; 504 | } 505 | 506 | .border-none{ border:0 } 507 | 508 | .rounded{ border-radius: 3px } 509 | .circle{ border-radius:50% } 510 | 511 | .rounded-top{ border-radius: 3px 3px 0 0 } 512 | .rounded-right{ border-radius: 0 3px 3px 0 } 513 | .rounded-bottom{ border-radius: 0 0 3px 3px } 514 | .rounded-left{ border-radius: 3px 0 0 3px } 515 | 516 | .not-rounded{ border-radius:0 } 517 | 518 | .hide{ 519 | position:absolute !important; 520 | height:1px; 521 | width:1px; 522 | overflow:hidden; 523 | clip:rect(1px, 1px, 1px, 1px); 524 | } 525 | 526 | @media (max-width: 40em){ 527 | .xs-hide{ display:none !important } 528 | } 529 | 530 | @media (min-width: 40em) and (max-width: 52em){ 531 | .sm-hide{ display:none !important } 532 | } 533 | 534 | @media (min-width: 52em) and (max-width: 64em){ 535 | .md-hide{ display:none !important } 536 | } 537 | 538 | @media (min-width: 64em){ 539 | .lg-hide{ display:none !important } 540 | } 541 | 542 | .display-none{ display:none !important } 543 | 544 | -------------------------------------------------------------------------------- /docs/assets/fonts/EOT/SourceCodePro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/EOT/SourceCodePro-Bold.eot -------------------------------------------------------------------------------- /docs/assets/fonts/EOT/SourceCodePro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/EOT/SourceCodePro-Regular.eot -------------------------------------------------------------------------------- /docs/assets/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /docs/assets/fonts/OTF/SourceCodePro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/OTF/SourceCodePro-Bold.otf -------------------------------------------------------------------------------- /docs/assets/fonts/OTF/SourceCodePro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/OTF/SourceCodePro-Regular.otf -------------------------------------------------------------------------------- /docs/assets/fonts/TTF/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/TTF/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterloftis/awaiting/e5cba28cc6f93c259d4dd5bbafea44d4d434fb08/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/source-code-pro.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Source Code Pro'; 3 | font-weight: 400; 4 | font-style: normal; 5 | font-stretch: normal; 6 | src: url('EOT/SourceCodePro-Regular.eot') format('embedded-opentype'), 7 | url('WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'), 8 | url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'), 9 | url('OTF/SourceCodePro-Regular.otf') format('opentype'), 10 | url('TTF/SourceCodePro-Regular.ttf') format('truetype'); 11 | } 12 | 13 | @font-face{ 14 | font-family: 'Source Code Pro'; 15 | font-weight: 700; 16 | font-style: normal; 17 | font-stretch: normal; 18 | src: url('EOT/SourceCodePro-Bold.eot') format('embedded-opentype'), 19 | url('WOFF2/TTF/SourceCodePro-Bold.ttf.woff2') format('woff2'), 20 | url('WOFF/OTF/SourceCodePro-Bold.otf.woff') format('woff'), 21 | url('OTF/SourceCodePro-Bold.otf') format('opentype'), 22 | url('TTF/SourceCodePro-Bold.ttf') format('truetype'); 23 | } 24 | -------------------------------------------------------------------------------- /docs/assets/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #1184CE; 31 | } 32 | 33 | .hljs-number, 34 | .hljs-hexcolor, 35 | .ruby .hljs-constant { 36 | color: #ed225d; 37 | } 38 | 39 | .hljs-string, 40 | .hljs-tag .hljs-value, 41 | .hljs-phpdoc, 42 | .hljs-dartdoc, 43 | .tex .hljs-formula { 44 | color: #ed225d; 45 | } 46 | 47 | .hljs-title, 48 | .hljs-id, 49 | .scss .hljs-preprocessor { 50 | color: #900; 51 | font-weight: bold; 52 | } 53 | 54 | .hljs-list .hljs-keyword, 55 | .hljs-subst { 56 | font-weight: normal; 57 | } 58 | 59 | .hljs-class .hljs-title, 60 | .hljs-type, 61 | .vhdl .hljs-literal, 62 | .tex .hljs-command { 63 | color: #458; 64 | font-weight: bold; 65 | } 66 | 67 | .hljs-tag, 68 | .hljs-tag .hljs-title, 69 | .hljs-rules .hljs-property, 70 | .django .hljs-tag .hljs-keyword { 71 | color: #000080; 72 | font-weight: normal; 73 | } 74 | 75 | .hljs-attribute, 76 | .hljs-variable, 77 | .lisp .hljs-body { 78 | color: #008080; 79 | } 80 | 81 | .hljs-regexp { 82 | color: #009926; 83 | } 84 | 85 | .hljs-symbol, 86 | .ruby .hljs-symbol .hljs-string, 87 | .lisp .hljs-keyword, 88 | .clojure .hljs-keyword, 89 | .scheme .hljs-keyword, 90 | .tex .hljs-special, 91 | .hljs-prompt { 92 | color: #990073; 93 | } 94 | 95 | .hljs-built_in { 96 | color: #0086b3; 97 | } 98 | 99 | .hljs-preprocessor, 100 | .hljs-pragma, 101 | .hljs-pi, 102 | .hljs-doctype, 103 | .hljs-shebang, 104 | .hljs-cdata { 105 | color: #999; 106 | font-weight: bold; 107 | } 108 | 109 | .hljs-deletion { 110 | background: #fdd; 111 | } 112 | 113 | .hljs-addition { 114 | background: #dfd; 115 | } 116 | 117 | .diff .hljs-change { 118 | background: #0086b3; 119 | } 120 | 121 | .hljs-chunk { 122 | color: #aaa; 123 | } 124 | -------------------------------------------------------------------------------- /docs/assets/site.js: -------------------------------------------------------------------------------- 1 | /* global anchors */ 2 | 3 | // add anchor links to headers 4 | anchors.options.placement = 'left'; 5 | anchors.add('h3'); 6 | 7 | // Filter UI 8 | var tocElements = document.getElementById('toc') 9 | .getElementsByTagName('li'); 10 | 11 | document.getElementById('filter-input') 12 | .addEventListener('keyup', function (e) { 13 | 14 | var i, element, children; 15 | 16 | // enter key 17 | if (e.keyCode === 13) { 18 | // go to the first displayed item in the toc 19 | for (i = 0; i < tocElements.length; i++) { 20 | element = tocElements[i]; 21 | if (!element.classList.contains('display-none')) { 22 | location.replace(element.firstChild.href); 23 | return e.preventDefault(); 24 | } 25 | } 26 | } 27 | 28 | var match = function () { 29 | return true; 30 | }; 31 | 32 | var value = this.value.toLowerCase(); 33 | 34 | if (!value.match(/^\s*$/)) { 35 | match = function (element) { 36 | return element.firstChild.innerHTML.toLowerCase().indexOf(value) !== -1; 37 | }; 38 | } 39 | 40 | for (i = 0; i < tocElements.length; i++) { 41 | element = tocElements[i]; 42 | children = Array.from(element.getElementsByTagName('li')); 43 | if (match(element) || children.some(match)) { 44 | element.classList.remove('display-none'); 45 | } else { 46 | element.classList.add('display-none'); 47 | } 48 | } 49 | }); 50 | 51 | var toggles = document.getElementsByClassName('toggle-step-sibling'); 52 | for (var i = 0; i < toggles.length; i++) { 53 | toggles[i].addEventListener('click', toggleStepSibling); 54 | } 55 | 56 | function toggleStepSibling() { 57 | var stepSibling = this.parentNode.parentNode.parentNode.getElementsByClassName('toggle-target')[0]; 58 | var klass = 'display-none'; 59 | if (stepSibling.classList.contains(klass)) { 60 | stepSibling.classList.remove(klass); 61 | stepSibling.innerHTML = '▾'; 62 | } else { 63 | stepSibling.classList.add(klass); 64 | stepSibling.innerHTML = '▸'; 65 | } 66 | } 67 | 68 | var items = document.getElementsByClassName('toggle-sibling'); 69 | for (var j = 0; j < items.length; j++) { 70 | items[j].addEventListener('click', toggleSibling); 71 | } 72 | 73 | function toggleSibling() { 74 | var stepSibling = this.parentNode.getElementsByClassName('toggle-target')[0]; 75 | var icon = this.getElementsByClassName('icon')[0]; 76 | var klass = 'display-none'; 77 | if (stepSibling.classList.contains(klass)) { 78 | stepSibling.classList.remove(klass); 79 | icon.innerHTML = '▾'; 80 | } else { 81 | stepSibling.classList.add(klass); 82 | icon.innerHTML = '▸'; 83 | } 84 | } 85 | 86 | function showHashTarget(targetId) { 87 | var hashTarget = document.getElementById(targetId); 88 | // new target is hidden 89 | if (hashTarget && hashTarget.offsetHeight === 0 && 90 | hashTarget.parentNode.parentNode.classList.contains('display-none')) { 91 | hashTarget.parentNode.parentNode.classList.remove('display-none'); 92 | } 93 | } 94 | 95 | window.addEventListener('hashchange', function() { 96 | showHashTarget(location.hash.substring(1)); 97 | }); 98 | 99 | showHashTarget(location.hash.substring(1)); 100 | 101 | var toclinks = document.getElementsByClassName('pre-open'); 102 | for (var k = 0; k < toclinks.length; k++) { 103 | toclinks[k].addEventListener('mousedown', preOpen, false); 104 | } 105 | 106 | function preOpen() { 107 | showHashTarget(this.hash.substring(1)); 108 | } 109 | -------------------------------------------------------------------------------- /docs/assets/style.css: -------------------------------------------------------------------------------- 1 | .documentation { 2 | font-family: Helvetica, sans-serif; 3 | color: #666; 4 | line-height: 1.5; 5 | background: #f5f5f5; 6 | } 7 | 8 | .black { 9 | color: #666; 10 | } 11 | 12 | .bg-white { 13 | background-color: #fff; 14 | } 15 | 16 | h4 { 17 | margin: 20px 0 10px 0; 18 | } 19 | 20 | .documentation h3 { 21 | color: #000; 22 | } 23 | 24 | .border-bottom { 25 | border-color: #ddd; 26 | } 27 | 28 | a { 29 | color: #1184CE; 30 | text-decoration: none; 31 | } 32 | 33 | .documentation a[href]:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | a:hover { 38 | cursor: pointer; 39 | } 40 | 41 | .py1-ul li { 42 | padding: 5px 0; 43 | } 44 | 45 | .max-height-100 { 46 | max-height: 100%; 47 | } 48 | 49 | section:target h3 { 50 | font-weight:700; 51 | } 52 | 53 | .documentation td, 54 | .documentation th { 55 | padding: .25rem .25rem; 56 | } 57 | 58 | h1:hover .anchorjs-link, 59 | h2:hover .anchorjs-link, 60 | h3:hover .anchorjs-link, 61 | h4:hover .anchorjs-link { 62 | opacity: 1; 63 | } 64 | 65 | .fix-3 { 66 | width: 25%; 67 | max-width: 244px; 68 | } 69 | 70 | .fix-3 { 71 | width: 25%; 72 | max-width: 244px; 73 | } 74 | 75 | @media (min-width: 52em) { 76 | .fix-margin-3 { 77 | margin-left: 25%; 78 | } 79 | } 80 | 81 | .pre, pre, code, .code { 82 | font-family: Source Code Pro,Menlo,Consolas,Liberation Mono,monospace; 83 | font-size: 14px; 84 | } 85 | 86 | .fill-light { 87 | background: #F9F9F9; 88 | } 89 | 90 | .width2 { 91 | width: 1rem; 92 | } 93 | 94 | .input { 95 | font-family: inherit; 96 | display: block; 97 | width: 100%; 98 | height: 2rem; 99 | padding: .5rem; 100 | margin-bottom: 1rem; 101 | border: 1px solid #ccc; 102 | font-size: .875rem; 103 | border-radius: 3px; 104 | box-sizing: border-box; 105 | } 106 | 107 | table { 108 | border-collapse: collapse; 109 | } 110 | 111 | .prose table th, 112 | .prose table td { 113 | text-align: left; 114 | padding:8px; 115 | border:1px solid #ddd; 116 | } 117 | 118 | .prose table th:nth-child(1) { border-right: none; } 119 | .prose table th:nth-child(2) { border-left: none; } 120 | 121 | .prose table { 122 | border:1px solid #ddd; 123 | } 124 | 125 | .prose-big { 126 | font-size: 18px; 127 | line-height: 30px; 128 | } 129 | 130 | .quiet { 131 | opacity: 0.7; 132 | } 133 | 134 | .minishadow { 135 | box-shadow: 2px 2px 10px #f3f3f3; 136 | } 137 | -------------------------------------------------------------------------------- /docs/examples/awaiting.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.awaiting = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o`** 20 | * 21 | * [![Build Status](https://travis-ci.org/hunterloftis/awaiting.svg?branch=master)](https://travis-ci.org/hunterloftis/awaiting) 22 | * [![Coverage Status](https://coveralls.io/repos/hunterloftis/awaiting/badge.svg?branch=master)](https://coveralls.io/r/hunterloftis/awaiting?branch=master) 23 | * 24 | * @license MIT 25 | * @file 26 | * @example 27 | * 28 | * const a = require('awaiting') 29 | * // ... 30 | * await a.delay(1000) 31 | */ 32 | 33 | module.exports = { 34 | delay, 35 | time, 36 | limit, 37 | event, 38 | callback, 39 | single, 40 | set, 41 | list, 42 | object, 43 | map, 44 | failure, 45 | success, 46 | result, 47 | awaited, 48 | awaitable: awaited, 49 | throw: throwRejections, 50 | swallow: swallowRejections, 51 | ErrorList 52 | } 53 | 54 | /** 55 | * Iterable Error type 56 | * 57 | * Functions that operate on lists throw ErrorLists, 58 | * making it possible to inspect all of the Errors that may have been thrown. 59 | * 60 | * @class 61 | * @param {string} message top-level Error message 62 | * @returns {iterable.} 63 | * @example 64 | * 65 | * const err = new ErrorList('several errors') 66 | * err.add(new Error('first')) 67 | * err.add(new Error('second')) 68 | * console.log(err.message, err.get(1).message) 69 | * // => several errors second 70 | * 71 | * @example 72 | * 73 | * try { 74 | * await a.list([ failing1, failing2, failing3 ]) 75 | * } 76 | * catch (errorList) { 77 | * for (let err of errorList) { 78 | * console.error(err.stack) 79 | * } 80 | * } 81 | */ 82 | function ErrorList (message) { 83 | this.name = 'ErrorList' 84 | this.message = message 85 | this.stack = (new Error()).stack 86 | this.errors = [] 87 | Object.defineProperty(this, 'length', { 88 | get: function () { return this.errors.length } 89 | }) 90 | } 91 | ErrorList.prototype = Object.create(Error.prototype) 92 | ErrorList.prototype.constructor = ErrorList 93 | ErrorList.prototype.add = function (err) { 94 | this.errors.push(err) 95 | } 96 | ErrorList.prototype.get = function (index) { 97 | return this.errors[index] 98 | } 99 | ErrorList.prototype[Symbol.iterator] = function* () { 100 | let i = 0 101 | while (i < this.errors.length) { 102 | yield this.errors[i] 103 | i++ 104 | } 105 | } 106 | 107 | /** 108 | * Waits for `ms` milliseconds to pass. 109 | * 110 | * @param {number} ms the number of milliseconds to wait 111 | * @returns {promise} 112 | * @example 113 | * 114 | * const start = Date.now() 115 | * await a.delay(5000) 116 | * console.log(Date.now() - start) 117 | * // => 5000 118 | */ 119 | async function delay (ms) { 120 | return new Promise((resolve, reject) => { 121 | setTimeout(resolve, ms) 122 | }) 123 | } 124 | 125 | /** 126 | * Waits for `date`. 127 | * 128 | * @param {date} date the date at which to stop waiting 129 | * @returns {promise} 130 | * @example 131 | * 132 | * const nextYear = new Date(2018, 1) 133 | * await a.time(nextYear) 134 | * // => this will run until the end of 2017 135 | */ 136 | async function time (date) { 137 | const delta = Math.max(date.getTime() - Date.now(), 0) 138 | return await delay(delta) 139 | } 140 | 141 | /** 142 | * Waits for the value of `goal`, limited by the resolution of `limiter`. 143 | * Throws an Error if `limiter` finishes first or if either throws early. 144 | * If `limiter` is a number, limits by time in milliseconds 145 | * 146 | * @param {promise} goal the promise to execute 147 | * @param {number|promise} limiter milliseconds or promise to limit by 148 | * @returns {promise} 149 | * @example 150 | * 151 | * // throw if flowers.jpg can't be retrieved in < 5 seconds 152 | * await a.limit(fetch('flowers.jpg'), 5000) 153 | */ 154 | async function limit (goal, limiter) { 155 | return new Promise((resolve, reject) => { 156 | const limitFn = typeof limiter === 'number' 157 | ? delay(limiter) 158 | : limiter 159 | let completed = false 160 | goal 161 | .then(result => { 162 | if (complete()) return 163 | resolve(result) 164 | }) 165 | .catch(err => { 166 | if (complete()) return 167 | reject(err) 168 | }) 169 | limitFn 170 | .then(result => { 171 | if (complete()) return 172 | reject(new Error('limit exceeded')) 173 | }) 174 | .catch(err => { 175 | if (complete()) return 176 | reject(err) 177 | }) 178 | function complete () { 179 | if (completed) return true 180 | completed = true 181 | return false 182 | } 183 | }) 184 | } 185 | 186 | /** 187 | * Waits for `emitter` to emit an `eventName` event. 188 | * 189 | * @param {EventEmitter} emitter the object to listen on 190 | * @param {string} eventName the event to listen for 191 | * @returns {promise.} an array of the arguments passed to the `eventName` event 192 | * @example 193 | * 194 | * await a.event(server, 'listen') 195 | */ 196 | async function event (emitter, eventName) { 197 | return new Promise((resolve, reject) => { 198 | emitter.once(eventName, (...args) => { 199 | resolve([...args]) 200 | }) 201 | }) 202 | } 203 | 204 | /** 205 | * Calls a function `func` that takes arguments `args` and an `(err, result)` callback. 206 | * Waits for the callback result, throwing an Error if err is truthy. 207 | * 208 | * @param {function} fn a function that takes a callback 209 | * @param {...object} args arguments to pass to `fn` 210 | * @returns {promise} the result passed to the callback 211 | * @example 212 | * 213 | * const result = await a.callback(fs.readFile, 'foo.txt') 214 | * console.log(result) 215 | * // => 'the text of the file' 216 | */ 217 | async function callback (fn, ...args) { 218 | return new Promise((resolve, reject) => { 219 | fn(...args, (err, result) => { 220 | if (err) return reject(err) 221 | resolve(result) 222 | }) 223 | }) 224 | } 225 | 226 | /** 227 | * Wraps a node style function (see `callback`) into a new function, which instead of taking a callback 228 | * function, returns an async function (`Promise`). This `Promise` resolves if the first (error) argument of the 229 | * callback was called with a falsy value, rejects with the error otherwise. Takes the rest of the 230 | * arguments as the original function `fn`. 231 | * 232 | * @returns {function} 233 | * @example 234 | * 235 | * const fs = require('fs') 236 | * const readFile = a.awaited(fs.readFile) 237 | * const contents = await readFile('foo.txt', 'utf-8') 238 | */ 239 | function awaited (fn) { 240 | return async (...args) => callback(fn, ...args) 241 | } 242 | 243 | function throwOnRejection (err, promise) { throw err } 244 | function swallowOnRejection (err, promise) {} // eslint-disable-line 245 | 246 | /** 247 | * Waits for the first Promise in `list` to resolve. 248 | * 249 | * @param {array.} list racing promises 250 | * @param {number} [ignore=0] number of rejections to ignore 251 | * @returns {promise} 252 | * @example 253 | * 254 | * const file = await a.single([ fetch(remoteFile), read(localFile) ]) 255 | */ 256 | async function single (list, ignore = 0) { 257 | const results = await set(list, 1, ignore) 258 | return results[0] 259 | } 260 | 261 | /** 262 | * Waits for the first `count` Promises in `list` to resolve. 263 | * 264 | * @param {array.} list racing promises 265 | * @param {number} [count=list.length] number of promises to wait for 266 | * @param {number} [ignore=0] number of rejections to ignore 267 | * @returns {promise.} 268 | * @example 269 | * 270 | * const [ first, second ] = await a.set([ 271 | * ping('ns1.example.com'), 272 | * ping('ns2.example.com'), 273 | * ping('ns3.example.com'), 274 | * ping('ns4.example.com') 275 | * ], 2) 276 | * console.log(`fastest nameservers: ${first}, ${second}`) 277 | */ 278 | 279 | async function set (list, count = Infinity, ignore = 0) { 280 | return new Promise((resolve, reject) => { 281 | const goal = Math.min(list.length, count) 282 | const limit = Math.min(list.length - goal, ignore) 283 | const results = [] 284 | const failures = new ErrorList('too many failures') 285 | list.forEach(promise => promise.then(success).catch(error)) 286 | 287 | function success (result) { 288 | if (failures.length > limit) return 289 | results.push(result) 290 | if (results.length === goal) { 291 | resolve(results) 292 | } 293 | } 294 | function error (err) { 295 | if (failures.length > limit) return 296 | if (results.length >= goal) return 297 | failures.add(err) 298 | // TODO: reject with an Iterable custom Error that includes all failures 299 | if (failures.length > limit) reject(failures) 300 | } 301 | }) 302 | } 303 | 304 | /** 305 | * Waits for all Promises in `list` to resolve. 306 | * 307 | * Like `Promise.all` with the option to ignore some (or all) rejections. 308 | * 309 | * @param {array} list promises 310 | * @param {number} ignore rejections to ignore 311 | * @returns {promise.} promised results in order 312 | * @example 313 | * 314 | * const results = await a.list([ foo, bar, baz ]) 315 | * console.log(results.length) 316 | * // => 3 317 | */ 318 | async function list (list, ignore = 0) { 319 | return new Promise((resolve, reject) => { 320 | const results = [] 321 | const failures = new ErrorList('too many failures') 322 | const complete = () => count + failures.length === list.length 323 | let count = 0 324 | list.forEach((promise, index) => { 325 | promise.then(success).catch(error) 326 | 327 | function success (result) { 328 | if (failures.length > ignore) return 329 | results[index] = result 330 | count++ 331 | if (complete()) resolve(results) 332 | } 333 | function error (err) { 334 | if (failures.length > ignore) return 335 | results[index] = undefined 336 | failures.add(err) 337 | if (failures.length > ignore) reject(failures) 338 | else if (complete()) resolve(results) 339 | } 340 | }) 341 | }) 342 | } 343 | 344 | /** 345 | * Waits for all Promises in the keys of `container` to resolve. 346 | * 347 | * @param {object} container 348 | * @param {number} ignore rejections to ignore 349 | * @returns {promise.} a new object with keys mapped to the resolved values 350 | * @example 351 | * 352 | * const results = await a.object({ 353 | * pictures: getPictures(), 354 | * comments: getComments(), 355 | * tweets: getTweets() 356 | * }) 357 | * console.log(results.pictures, results.comments, results.tweets) 358 | */ 359 | 360 | async function object (container, ignore = 0) { 361 | const containsPromise = key => typeof container[key].then === 'function' 362 | const keys = Object.keys(container).filter(containsPromise) 363 | const promises = keys.map(key => container[key]) 364 | const results = await list(promises, ignore) 365 | const obj = Object.assign({}, container) 366 | results.forEach((result, index) => { 367 | const key = keys[index] 368 | obj[key] = result 369 | }) 370 | return obj 371 | } 372 | 373 | /** 374 | * Passes each item in `list` to the Promise-returning function `fn`, 375 | * running at most `concurrency` simultaneous promises. 376 | * 377 | * For cases where starting all Promises simultaneously is infeasible, 378 | * such as making a large number of requests to a server with rate-limiting. 379 | * 380 | * @param {array} list items to pass to each promise 381 | * @param {number} concurrency maximum concurrency 382 | * @param {function} fn takes an item and returns a Promise 383 | * @example 384 | * 385 | * // pull hundreds of pages from a site without getting blocked 386 | * const pages = await a.map(urls, 3, fetch) 387 | */ 388 | async function map (list, concurrency, fn) { 389 | return new Promise((resolve, reject) => { 390 | const results = [] 391 | let running = 0 392 | let index = 0 393 | 394 | update() 395 | 396 | function update () { 397 | if (index === list.length && running === 0) { 398 | return resolve(results) 399 | } 400 | while (running < concurrency && index < list.length) { 401 | fn(list[index]).then(success(index)).catch(error) 402 | index++ 403 | running++ 404 | } 405 | } 406 | function success (i) { 407 | return result => { 408 | running-- 409 | results[i] = result 410 | update() 411 | } 412 | } 413 | function error (err) { 414 | running-- 415 | index = Infinity 416 | reject(err) 417 | } 418 | }) 419 | } 420 | 421 | /** 422 | * Waits for `promise` to reject, returning the Error object. 423 | * If `promise` resolves successfully, returns `undefined`. 424 | * 425 | * @param {promise} 426 | * @returns {promise.} the Error object, or undefined 427 | * @example 428 | * 429 | * test('throws "foo"', () => { 430 | * const err = await a.failure(shouldThrow()) 431 | * assert.equal(err.message, 'foo') 432 | * }) 433 | */ 434 | async function failure (promise) { 435 | return new Promise((resolve, reject) => { 436 | promise.then(() => resolve(undefined)).catch(resolve) 437 | }) 438 | } 439 | 440 | /** 441 | * Waits for the value of `promise`. 442 | * If `promise` throws an Error, returns `undefined`. 443 | * 444 | * @param {promise} 445 | * @returns {promise} the result, or undefined 446 | * @example 447 | * 448 | * const isNodeProject = await a.success(a.callback(fs.access, packageJSON)) 449 | * if (isNodeProject) doSomething() 450 | */ 451 | async function success (promise) { 452 | return new Promise((resolve, reject) => { 453 | promise.then(resolve).catch(err => resolve(undefined)) // eslint-disable-line 454 | }) 455 | } 456 | 457 | /** 458 | * Waits for `promise` to resolve or reject. 459 | * Returns either the resolved value, or the Error object. 460 | * 461 | * @param {promise} 462 | * @returns {promise} the result or error 463 | * @example 464 | * 465 | * $("#ajax-loader-animation").show() 466 | * await a.result(loadAjaxData()) 467 | * $("#ajax-loader-animation").hide(); 468 | */ 469 | async function result (promise) { 470 | return new Promise((resolve, reject) => { 471 | promise.then(resolve).catch(err => resolve(err)) 472 | }) 473 | } 474 | 475 | /** 476 | * Provides a stack trace for unhandled rejections instead of the default message string. 477 | * 478 | * `throw` and `swallow` can be called multiple times but will only attach a single listener. 479 | * 480 | * @alias throw 481 | * @returns {undefined} 482 | * @example 483 | * 484 | * failingPromise() 485 | * // => (node:6051) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail 486 | * 487 | * @example 488 | * 489 | * a.throw() 490 | * failingPromise() 491 | * // => /Users/hloftis/code/awaiting/lib/awaiting.js:308 492 | * // => function throwOnRejection (err, promise) { throw err } 493 | * // => ^ 494 | * // => Error: fail 495 | * // => at fail (/Users/hloftis/code/awaiting/test/fixtures/rejection-throw.js:7:9) 496 | * // => at Object. (/Users/hloftis/code/awaiting/test/fixtures/rejection-throw.js:4:1) 497 | */ 498 | function throwRejections () { 499 | process.removeListener('unhandledRejection', throwOnRejection) 500 | process.removeListener('unhandledRejection', swallowOnRejection) 501 | process.on('unhandledRejection', throwOnRejection) 502 | } 503 | 504 | /** 505 | * Silently swallows unhandled rejections. 506 | * 507 | * This is an anti-pattern, but if you depend on a module that doesn't handle all of its rejections, 508 | * you'll get a lot of really annoying logs in current versions of node. 509 | * `swallow` will allow you to suppress them. 510 | * 511 | * `throw` and `swallow` can be called multiple times but will only attach a single listener. 512 | * 513 | * @alias swallow 514 | * @returns {undefined} 515 | * @example 516 | * 517 | * failingPromise() 518 | * // => (node:6051) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail 519 | * 520 | * @example 521 | * 522 | * a.swallow() 523 | * failingPromise() 524 | * // (no output) 525 | */ 526 | function swallowRejections () { 527 | process.removeListener('unhandledRejection', throwOnRejection) 528 | process.removeListener('unhandledRejection', swallowOnRejection) 529 | process.on('unhandledRejection', swallowOnRejection) 530 | } 531 | 532 | }).call(this,require('_process')) 533 | },{"_process":2}],2:[function(require,module,exports){ 534 | // shim for using process in browser 535 | var process = module.exports = {}; 536 | 537 | // cached from whatever global is present so that test runners that stub it 538 | // don't break things. But we need to wrap it in a try catch in case it is 539 | // wrapped in strict mode code which doesn't define any globals. It's inside a 540 | // function because try/catches deoptimize in certain engines. 541 | 542 | var cachedSetTimeout; 543 | var cachedClearTimeout; 544 | 545 | function defaultSetTimout() { 546 | throw new Error('setTimeout has not been defined'); 547 | } 548 | function defaultClearTimeout () { 549 | throw new Error('clearTimeout has not been defined'); 550 | } 551 | (function () { 552 | try { 553 | if (typeof setTimeout === 'function') { 554 | cachedSetTimeout = setTimeout; 555 | } else { 556 | cachedSetTimeout = defaultSetTimout; 557 | } 558 | } catch (e) { 559 | cachedSetTimeout = defaultSetTimout; 560 | } 561 | try { 562 | if (typeof clearTimeout === 'function') { 563 | cachedClearTimeout = clearTimeout; 564 | } else { 565 | cachedClearTimeout = defaultClearTimeout; 566 | } 567 | } catch (e) { 568 | cachedClearTimeout = defaultClearTimeout; 569 | } 570 | } ()) 571 | function runTimeout(fun) { 572 | if (cachedSetTimeout === setTimeout) { 573 | //normal enviroments in sane situations 574 | return setTimeout(fun, 0); 575 | } 576 | // if setTimeout wasn't available but was latter defined 577 | if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { 578 | cachedSetTimeout = setTimeout; 579 | return setTimeout(fun, 0); 580 | } 581 | try { 582 | // when when somebody has screwed with setTimeout but no I.E. maddness 583 | return cachedSetTimeout(fun, 0); 584 | } catch(e){ 585 | try { 586 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 587 | return cachedSetTimeout.call(null, fun, 0); 588 | } catch(e){ 589 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error 590 | return cachedSetTimeout.call(this, fun, 0); 591 | } 592 | } 593 | 594 | 595 | } 596 | function runClearTimeout(marker) { 597 | if (cachedClearTimeout === clearTimeout) { 598 | //normal enviroments in sane situations 599 | return clearTimeout(marker); 600 | } 601 | // if clearTimeout wasn't available but was latter defined 602 | if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { 603 | cachedClearTimeout = clearTimeout; 604 | return clearTimeout(marker); 605 | } 606 | try { 607 | // when when somebody has screwed with setTimeout but no I.E. maddness 608 | return cachedClearTimeout(marker); 609 | } catch (e){ 610 | try { 611 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 612 | return cachedClearTimeout.call(null, marker); 613 | } catch (e){ 614 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. 615 | // Some versions of I.E. have different rules for clearTimeout vs setTimeout 616 | return cachedClearTimeout.call(this, marker); 617 | } 618 | } 619 | 620 | 621 | 622 | } 623 | var queue = []; 624 | var draining = false; 625 | var currentQueue; 626 | var queueIndex = -1; 627 | 628 | function cleanUpNextTick() { 629 | if (!draining || !currentQueue) { 630 | return; 631 | } 632 | draining = false; 633 | if (currentQueue.length) { 634 | queue = currentQueue.concat(queue); 635 | } else { 636 | queueIndex = -1; 637 | } 638 | if (queue.length) { 639 | drainQueue(); 640 | } 641 | } 642 | 643 | function drainQueue() { 644 | if (draining) { 645 | return; 646 | } 647 | var timeout = runTimeout(cleanUpNextTick); 648 | draining = true; 649 | 650 | var len = queue.length; 651 | while(len) { 652 | currentQueue = queue; 653 | queue = []; 654 | while (++queueIndex < len) { 655 | if (currentQueue) { 656 | currentQueue[queueIndex].run(); 657 | } 658 | } 659 | queueIndex = -1; 660 | len = queue.length; 661 | } 662 | currentQueue = null; 663 | draining = false; 664 | runClearTimeout(timeout); 665 | } 666 | 667 | process.nextTick = function (fun) { 668 | var args = new Array(arguments.length - 1); 669 | if (arguments.length > 1) { 670 | for (var i = 1; i < arguments.length; i++) { 671 | args[i - 1] = arguments[i]; 672 | } 673 | } 674 | queue.push(new Item(fun, args)); 675 | if (queue.length === 1 && !draining) { 676 | runTimeout(drainQueue); 677 | } 678 | }; 679 | 680 | // v8 likes predictible objects 681 | function Item(fun, array) { 682 | this.fun = fun; 683 | this.array = array; 684 | } 685 | Item.prototype.run = function () { 686 | this.fun.apply(null, this.array); 687 | }; 688 | process.title = 'browser'; 689 | process.browser = true; 690 | process.env = {}; 691 | process.argv = []; 692 | process.version = ''; // empty string to avoid regexp issues 693 | process.versions = {}; 694 | 695 | function noop() {} 696 | 697 | process.on = noop; 698 | process.addListener = noop; 699 | process.once = noop; 700 | process.off = noop; 701 | process.removeListener = noop; 702 | process.removeAllListeners = noop; 703 | process.emit = noop; 704 | 705 | process.binding = function (name) { 706 | throw new Error('process.binding is not supported'); 707 | }; 708 | 709 | process.cwd = function () { return '/' }; 710 | process.chdir = function (dir) { 711 | throw new Error('process.chdir is not supported'); 712 | }; 713 | process.umask = function() { return 0; }; 714 | 715 | },{}]},{},[1])(1) 716 | }); -------------------------------------------------------------------------------- /docs/examples/awaiting.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (factory((global.awaiting = global.awaiting || {}))); 5 | }(this, (function (exports) { 'use strict'; 6 | 7 | /** 8 | * The async/await utility for browsers and Node.js. 9 | * 10 | * ( 11 | * [examples](https://github.com/hunterloftis/awaiting#examples) - 12 | * [github](https://github.com/hunterloftis/awaiting) - 13 | * [npm](https://www.npmjs.com/package/awaiting) - 14 | * [suggestions / bug reports](https://github.com/hunterloftis/awaiting/issues) - 15 | * [installation](https://github.com/hunterloftis/awaiting#installation) - 16 | * [motivation](https://github.com/hunterloftis/awaiting#motivation) 17 | * ) 18 | * 19 | * **`$ yarn add awaiting`** 20 | * 21 | * **`$ npm install awaiting --save`** 22 | * 23 | * **` 7 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | background: #f5f5f5; 3 | } 4 | 5 | body { 6 | background: #fff; 7 | width: 1000px; 8 | margin: 10px auto; 9 | padding: 17px; 10 | font-family: Helvetica, sans-serif; 11 | color: rgb(102, 102, 102) 12 | } 13 | 14 | h1, h2, h3, h4 { 15 | font-weight: normal; 16 | color: #000; 17 | } 18 | 19 | h2 { 20 | margin-top: 50px; 21 | } 22 | 23 | pre, ul, li { 24 | text-align: left; 25 | } 26 | 27 | pre { 28 | font-family: Source Code Pro,Menlo,Consolas,Liberation Mono,monospace; 29 | color: #24292e; 30 | font-size: 11pt; 31 | background: #F9F9F9; 32 | padding: 20px; 33 | } 34 | 35 | a { 36 | color: #1184CE; 37 | text-decoration: none; 38 | } 39 | -------------------------------------------------------------------------------- /examples/awaiting.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (factory((global.awaiting = global.awaiting || {}))); 5 | }(this, (function (exports) { 'use strict'; 6 | 7 | /** 8 | * The async/await utility for browsers and Node.js. 9 | * 10 | * ( 11 | * [examples](https://github.com/hunterloftis/awaiting#examples) - 12 | * [github](https://github.com/hunterloftis/awaiting) - 13 | * [npm](https://www.npmjs.com/package/awaiting) - 14 | * [suggestions / bug reports](https://github.com/hunterloftis/awaiting/issues) - 15 | * [installation](https://github.com/hunterloftis/awaiting#installation) - 16 | * [motivation](https://github.com/hunterloftis/awaiting#motivation) 17 | * ) 18 | * 19 | * **`$ yarn add awaiting`** 20 | * 21 | * **`$ npm install awaiting --save`** 22 | * 23 | * **` 7 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/awaiting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The async/await utility for browsers and Node.js. 3 | * 4 | * ( 5 | * [examples](https://github.com/hunterloftis/awaiting#examples) - 6 | * [github](https://github.com/hunterloftis/awaiting) - 7 | * [npm](https://www.npmjs.com/package/awaiting) - 8 | * [suggestions / bug reports](https://github.com/hunterloftis/awaiting/issues) - 9 | * [installation](https://github.com/hunterloftis/awaiting#installation) - 10 | * [motivation](https://github.com/hunterloftis/awaiting#motivation) 11 | * ) 12 | * 13 | * **`$ yarn add awaiting`** 14 | * 15 | * **`$ npm install awaiting --save`** 16 | * 17 | * **` 53 | 62 | ``` 63 | 64 | ### With Babel 65 | 66 | `import` whatever you want, you hipster. 67 | 68 | ## Examples 69 | 70 | Check out a 71 | [live example in your browser](https://hunterloftis.github.io/awaiting/examples/kittens.html), 72 | then [view the source](examples/kittens.html). 73 | 74 | ```js 75 | // wait for a second 76 | await a.delay(1000) 77 | 78 | // limit a fetch to five seconds 79 | const image = await a.limit(fetch('flowers.jpg'), 5000) 80 | 81 | // await events 82 | await a.event(server, 'listen') 83 | 84 | // await callbacks 85 | const contents = await a.callback(fs.readFile, 'foo.txt') 86 | 87 | // map an array to an async function with 3 instances running in parallel 88 | const pages = await a.map(urls, 3, fetch) 89 | 90 | // await successes (ignoring errors) 91 | const file = await a.success(getNetworkFile()) 92 | ``` 93 | 94 | ...and more in [the API Docs](https://hunterloftis.github.io/awaiting). 95 | 96 | ## Motivation 97 | 98 | **I love the async/await pattern.** 99 | Code written with async functions benefits from superior readability, 100 | improved terseness and expressiveness, and unified error handling. 101 | No more nested callbacks, opaque Promise chains, and `if (err)` checks littering your code. 102 | 103 | **However, this pattern isn't a panacea.** 104 | It's easy to do some things: 105 | iterate through single items, wait on a single result, run an array of promises in parallel. 106 | Other workflows require abstraction or state. 107 | I kept finding myself writing the same utility functions in each project: 108 | delays, throttled maps, skipping try/catch on optional operations, adapting to events or callbacks. 109 | Await, combined with these simple abstractions, yields readable yet powerful async workflows. 110 | 111 | **Why now?** 112 | Node v7.6.0 enabled non-transpiled, non-flagged, out-of-the-box support for async functions. 113 | Every major browser has shipped support. 114 | If you write for old clients, you can still use this via Babel. 115 | Async functions are already here. 116 | 117 | **Why not something like Bluebird?** 118 | This is heavily inspired by libraries like 119 | [Bluebird](http://bluebirdjs.com/docs/getting-started.html) 120 | and [Async](https://github.com/caolan/async), 121 | which both aim to make non-trivial async workflows more readable. 122 | 123 | However, these problems shouldn't be solved by replacing native Promise implementations with custom versions, 124 | as Bluebird and Q attempt. 125 | Having multiple, conflicting definitions of Promise in a codebase means you now have to check 126 | the capabilities of a given Promise before using it. 127 | This decreases interoperability and increases fragmentation - and dependency bloat. 128 | It's not uncommon for a single app to depend on two or three subtly different Promise implementations. 129 | 130 | We've been here before, back when extending Object prototypes was the norm. 131 | We've seen how painful it is to have different libraries extending or replacing 132 | built-ins like Promise with conflicting implementations of custom behavior. 133 | 134 | Node's ['unhandledRejection' event](https://nodejs.org/api/process.html#process_event_unhandledrejection) 135 | illustrates why interoperability is so important: 136 | if you're using non-standard Promises, you can't catch that event. 137 | If your app and dependencies use a *mix* of 3rd party and native Promises, 138 | *some* of the Promise rejections in your app will be caught while others are not. 139 | If you've ever used a library that returned some sort of "Promise," 140 | but you had to dive into the source to find out exactly which implementation and custom behavior it exposed, 141 | you've also experienced the pain of fragmentation. 142 | 143 | Instead, awaiting follows the example of 144 | [lodash](https://lodash.com/) and 145 | [underscore](http://underscorejs.org/), 146 | which chose not to replace or extend native Arrays and Objects, but instead provided functional utilities for them. 147 | 148 | ## API Overview 149 | 150 | This illustrates use cases for each utility. 151 | For details, see the [full API docs](https://hunterloftis.github.io/awaiting/). 152 | 153 | Use this when you want to... 154 | 155 | - **callback:** treat a callback function (like one of the core node APIs) as an async function. 156 | - **delay:** wait for some time to pass. 157 | - **event:** treat an EventEmitter's event (like `server.on('listen')`) as an async function. 158 | - **failure:** inspect the Error from a probable failure (vs throwing / exiting) 159 | - **limit:** limit the runtime of an async function so your program doesn't hang. 160 | - **list:** wait for a list of async functions to resolve simultaneously, possibly ignoring some number of rejections. 161 | - **map:** wait for a list of async functions to resolve, limiting how many run simultaneously to avoid running out of memory or hammering a server with too many requests. 162 | - **object:** resolve several async functions simultaneously which are stored as properties of an object. 163 | - **result:** wait for an async function to resolve or reject, then check to see whether it returned a result or an Error. 164 | - **set:** wait for a minimum set of async functions to resolve, such as pinging a dozen servers and seeing which two are fastest. 165 | - **single:** wait for a single async function to resolve from a list. 166 | - **success:** ignore the result of an async function (`undefined`) if it fails. 167 | - **swallow:** use someone else's module that throws a lot of unhandled rejection errors. 168 | - **throw:** get useful stack traces from your unhandled rejections instead of just console logs. 169 | - **time:** wait for a specific time (as a `Date` object). 170 | 171 | ## Building 172 | 173 | ``` 174 | $ yarn build 175 | ``` 176 | 177 | ## Testing 178 | 179 | ``` 180 | $ yarn install 181 | $ yarn test 182 | $ yarn test:browser 183 | ``` 184 | -------------------------------------------------------------------------------- /test/awaited.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('awaited', () => { 5 | const awaitedRead = a.awaited(read) 6 | 7 | it('awaited correctly handles resolution', async () => { 8 | const result = await awaitedRead('foo.txt') 9 | assert.equal(result, 'contents') 10 | }) 11 | 12 | it('awaited correctly handles rejection', async () => { 13 | try { 14 | const result = await awaitedRead('bar.txt') 15 | throw new Error('should fail') 16 | } 17 | catch (err) { 18 | assert.equal(err.message, 'file not found') 19 | } 20 | }) 21 | }) 22 | 23 | function read(file, callback) { 24 | if (file !== 'foo.txt') { 25 | setTimeout(() => callback(new Error('file not found')), 0) 26 | return 27 | } 28 | setTimeout(() => callback(null, 'contents'), 0) 29 | } 30 | -------------------------------------------------------------------------------- /test/awaiting.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('awaiting', () => { 5 | it('returns an object', () => { 6 | assert.isObject(a) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/callback.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('callback', () => { 5 | it('passes arguments and receives results', async () => { 6 | const result = await a.callback(read, 'foo.txt') 7 | assert.equal(result, 'contents') 8 | }) 9 | it('throws an error if (err) is truthy', async () => { 10 | try { 11 | const result = await a.callback(read, 'bar.txt') 12 | throw new Error('should fail') 13 | } 14 | catch (err) { 15 | assert.equal(err.message, 'file not found') 16 | } 17 | }) 18 | }) 19 | 20 | function read(file, callback) { 21 | if (file !== 'foo.txt') { 22 | setTimeout(() => callback(new Error('file not found')), 0) 23 | return 24 | } 25 | setTimeout(() => callback(null, 'contents'), 0) 26 | } 27 | -------------------------------------------------------------------------------- /test/delay.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('delay', () => { 5 | it('delays by (ms) milliseconds', async () => { 6 | const start = Date.now() 7 | await a.delay(100) 8 | assert.closeTo(Date.now() - start, 100, 10) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/errorlist.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('ErrorList', () => { 5 | it('can be thrown', () => { 6 | try { 7 | throw new a.ErrorList('testing') 8 | throw new Error('should not get here') 9 | } 10 | catch (err) { 11 | assert.equal(err.message, 'testing') 12 | } 13 | }) 14 | it('can be added to', () => { 15 | const err = new a.ErrorList('foo') 16 | err.add(new Error('bar')) 17 | assert.equal(err.get(0).message, 'bar') 18 | }) 19 | it('can be iterated over', () => { 20 | const list = new a.ErrorList('foo') 21 | const messages = [] 22 | list.add(new Error('bar')) 23 | list.add(new Error('baz')) 24 | for (let err of list) { 25 | messages.push(err.message) 26 | } 27 | assert.deepEqual(messages, ['bar', 'baz']) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/failure.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('failure', () => { 5 | it('returns an Error object on error', async () => { 6 | const err = await a.failure(fail()) 7 | assert.equal(err.message, 'fail') 8 | }) 9 | it('returns undefined on success', async () => { 10 | const result = await a.failure(succeed()) 11 | assert.isUndefined(result) 12 | }) 13 | 14 | async function fail() { 15 | await a.delay(10) 16 | throw new Error('fail') 17 | } 18 | 19 | async function succeed() { 20 | await a.delay(10) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /test/fixtures/rejection-ignore.js: -------------------------------------------------------------------------------- 1 | fail() 2 | 3 | async function fail() { 4 | throw new Error('fail') 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/rejection-multiple.js: -------------------------------------------------------------------------------- 1 | const a = require('../..') 2 | 3 | a.swallow() 4 | a.throw() 5 | a.swallow() 6 | a.throw() 7 | a.throw() 8 | fail() 9 | 10 | async function fail() { 11 | throw new Error('fail') 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/rejection-swallow.js: -------------------------------------------------------------------------------- 1 | const a = require('../..') 2 | 3 | a.swallow() 4 | fail() 5 | 6 | async function fail() { 7 | throw new Error('fail') 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/rejection-throw.js: -------------------------------------------------------------------------------- 1 | const a = require('../..') 2 | 3 | a.throw() 4 | fail() 5 | 6 | async function fail() { 7 | throw new Error('fail') 8 | } 9 | -------------------------------------------------------------------------------- /test/limit.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('limit', () => { 5 | it('resolves to `goal` if `goal` finishes first', async () => { 6 | const result = await a.limit(goal('foo', 10), limit(50)) 7 | assert.equal(result, 'foo') 8 | }) 9 | 10 | it('throws an error if `limiter` finishes first', async () => { 11 | try { 12 | const result = await a.limit(goal('bar', 50), limit(10)) 13 | throw new Error('should fail') 14 | } 15 | catch (err) { 16 | assert.equal(err.message, 'limit exceeded') 17 | } 18 | }) 19 | 20 | it('re-throws an error if `goal` throws early', async () => { 21 | try { 22 | const result = await a.limit(fail(10), limit(50)) 23 | throw new Error('should fail') 24 | } 25 | catch (err) { 26 | assert.equal(err.message, 'fail') 27 | } 28 | }) 29 | 30 | it('swallows an error if `goal` throws late', async () => { 31 | try { 32 | const result = await a.limit(fail(50), limit(10)) 33 | throw new Error('should fail') 34 | } 35 | catch (err) { 36 | assert.equal(err.message, 'limit exceeded') 37 | } 38 | }) 39 | 40 | it('re-throws an error if `limiter` throws early', async () => { 41 | try { 42 | const result = await a.limit(goal('foo', 50), fail(10)) 43 | throw new Error('should fail') 44 | } 45 | catch (err) { 46 | assert.equal(err.message, 'fail') 47 | } 48 | }) 49 | 50 | it('swallows an error if `limiter` throws late', async () => { 51 | const result = await a.limit(goal('bar', 10), fail(50)) 52 | assert.equal(result, 'bar') 53 | }) 54 | 55 | it('limits by milliseconds if `limiter` is a Number', async () => { 56 | try { 57 | const result = await a.limit(goal('foo', 50), 10) 58 | } 59 | catch (err) { 60 | assert.equal(err.message, 'limit exceeded') 61 | } 62 | }) 63 | 64 | async function goal(value, ms) { 65 | await a.delay(ms) 66 | return value 67 | } 68 | 69 | async function limit(ms) { 70 | await a.delay(ms) 71 | } 72 | 73 | async function fail(ms) { 74 | await a.delay(ms) 75 | throw new Error('fail') 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /test/list.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('list', () => { 5 | it('returns the results of all promises', async () => { 6 | const results = await a.list([ p('foo'), p('bar'), p('baz') ]) 7 | assert.deepEqual(results, ['foo', 'bar', 'baz']) 8 | }) 9 | it('throws if one of the results throws', async () => { 10 | try { 11 | const results = await a.list([ f(120), p('foo', 100), f(50), p('baz', 10)]) 12 | throw new Error('should fail') 13 | } 14 | catch (err) { 15 | assert.equal(err.message, 'too many failures') 16 | assert.equal(err.get(0).message, 'fail') 17 | } 18 | }) 19 | it("doesn't throw if failures < ignore", async () => { 20 | const results = await a.list([ p('foo', 100), f(50), p('baz', 10)], 1) 21 | assert.deepEqual(results, ['foo', undefined, 'baz']) 22 | }) 23 | it('resolves if an error is the last promise to complete', async () => { 24 | const results = await a.list([ f(100), p('bar', 50), p('baz', 10)], 1) 25 | assert.deepEqual(results, [undefined, 'bar', 'baz']) 26 | }) 27 | }) 28 | 29 | function p(val, delay = 0) { 30 | return new Promise((resolve, reject) => { 31 | setTimeout(() => resolve(val), delay) 32 | }) 33 | } 34 | 35 | async function f(delay) { 36 | await a.delay(delay) 37 | throw new Error('fail') 38 | } 39 | -------------------------------------------------------------------------------- /test/map.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('map', () => { 5 | it('maps all items to promises', async () => { 6 | let results = await a.map(['foo', 'bar', 'baz'], 1, prefix) 7 | assert.deepEqual(results, ['p-foo', 'p-bar', 'p-baz']) 8 | }) 9 | it('throws if anything rejects', async () => { 10 | try { 11 | await a.map(['foo', 'bar', 'baz'], 2, hatesBar) 12 | throw new Error('should fail') 13 | } 14 | catch (err) { 15 | assert.equal(err.message, 'bar sucks') 16 | } 17 | }) 18 | it('throttles concurrency', async () => { 19 | let max = 0 20 | let current = 0 21 | await a.map([10, 20, 30, 10, 50, 100, 0, 50, 10], 3, maximum) 22 | assert.equal(max, 3) 23 | 24 | async function maximum(ms) { 25 | current++ 26 | max = Math.max(current, max) 27 | await a.delay(ms) 28 | current-- 29 | } 30 | }) 31 | 32 | async function prefix(val) { 33 | await a.delay(10) 34 | return `p-${val}` 35 | } 36 | 37 | async function hatesBar(val) { 38 | await a.delay(10) 39 | if (val === 'bar') throw new Error('bar sucks') 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /test/node/event.test.js: -------------------------------------------------------------------------------- 1 | const a = require('../..') 2 | const assert = require('chai').assert 3 | const EventEmitter = require('events').EventEmitter 4 | 5 | describe('event', () => { 6 | it('waits until the event is emitted', async () => { 7 | const emitter = Emitter('listen', 100) 8 | const start = Date.now() 9 | assert.equal(emitter.emitted, false) 10 | await a.event(emitter, 'listen') 11 | assert.closeTo(Date.now() - start, 100, 10) 12 | assert.equal(emitter.emitted, true) 13 | }) 14 | it("returns an array of the event's arguments", async () => { 15 | const emitter = Emitter('close', 10, 'foo', 123) 16 | const results = await a.event(emitter, 'close') 17 | assert.deepEqual(results, ['foo', 123]) 18 | }) 19 | }) 20 | 21 | function Emitter(eventName, ms, ...args) { 22 | const emitter = new EventEmitter() 23 | emitter.emitted = false 24 | 25 | setTimeout(() => { 26 | emitter.emitted = true 27 | emitter.emit(eventName, ...args) 28 | }, ms) 29 | 30 | return emitter 31 | } 32 | -------------------------------------------------------------------------------- /test/node/rejections.test.js: -------------------------------------------------------------------------------- 1 | const a = require('../..') 2 | const assert = require('chai').assert 3 | const spawnSync = require('child_process').spawnSync 4 | const join = require('path').join 5 | 6 | describe('rejections', () => { 7 | describe('ignored', () => { 8 | it('logs useless console logs', () => { 9 | const fixture = join(__dirname, '../fixtures/rejection-ignore.js') 10 | const results = spawnSync('node', [fixture], { encoding: 'utf8' }) 11 | const output = results.output.join('\n') 12 | assert.include(output, 'UnhandledPromiseRejectionWarning:') 13 | assert.notInclude(output, 'at fail') 14 | }) 15 | }) 16 | describe('throw', () => { 17 | it('throws on unhandled rejections', () => { 18 | const fixture = join(__dirname, '../fixtures/rejection-throw.js') 19 | const results = spawnSync('node', [fixture], { encoding: 'utf8' }) 20 | const output = results.output.join('\n') 21 | assert.include(output, 'Error: fail') 22 | assert.include(output, 'at fail') 23 | assert.include(output, 'at Object') 24 | assert.notInclude(output, 'UnhandledPromiseRejectionWarning:') 25 | }) 26 | it('only throws once', () => { 27 | const fixture = join(__dirname, '../fixtures/rejection-multiple.js') 28 | const results = spawnSync('node', [fixture], { encoding: 'utf8' }) 29 | const output = results.output.join('\n') 30 | const count = output.match(/Error: fail/g).length 31 | assert.equal(count, 1) 32 | }) 33 | }) 34 | describe('swallow', () => { 35 | it('hides unhandled rejections', () => { 36 | const fixture = join(__dirname, '../fixtures/rejection-swallow.js') 37 | const results = spawnSync('node', [fixture], { encoding: 'utf8' }) 38 | const output = results.output.join('\n') 39 | assert.notInclude(output, 'Error: fail') 40 | assert.notInclude(output, 'UnhandledPromiseRejectionWarning:') 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/object.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('object', () => { 5 | it('returns the results of all promises', async () => { 6 | const results = await a.object({ 7 | foo: p('foo'), 8 | bar: p('bar'), 9 | baz: p('baz') 10 | }) 11 | assert.deepEqual(results, { foo: 'foo', bar: 'bar', baz: 'baz' }) 12 | }) 13 | it('throws if one of the results throws', async () => { 14 | try { 15 | const results = await a.object({ 16 | foo: p('foo', 100), 17 | bar: f(50), 18 | baz: p('baz', 10) 19 | }) 20 | throw new Error('should fail') 21 | } 22 | catch (err) { 23 | assert.equal(err.message, 'too many failures') 24 | assert.equal(err.get(0).message, 'fail') 25 | } 26 | }) 27 | it("doesn't throw if failures < ignore", async () => { 28 | const results = await a.object({ 29 | foo: p('foo', 100), 30 | bar: f(50), 31 | baz: p('baz', 10) 32 | }, 1) 33 | assert.deepEqual(results, { foo: 'foo', bar: undefined, baz: 'baz' }) 34 | }) 35 | }) 36 | 37 | function p(val, delay = 0) { 38 | return new Promise((resolve, reject) => { 39 | setTimeout(() => resolve(val), delay) 40 | }) 41 | } 42 | 43 | async function f(delay) { 44 | await a.delay(delay) 45 | throw new Error('fail') 46 | } 47 | -------------------------------------------------------------------------------- /test/result.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('result', () => { 5 | it('returns a value on success', async () => { 6 | const result = await a.result(succeed()) 7 | assert.equal(result, 'ok') 8 | }) 9 | it('returns the Error on error', async () => { 10 | const result = await a.result(fail()) 11 | assert.equal(result.message, 'fail') 12 | }) 13 | 14 | async function fail() { 15 | await a.delay(10) 16 | throw new Error('fail') 17 | } 18 | 19 | async function succeed() { 20 | await a.delay(10) 21 | return 'ok' 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /test/set.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('set', () => { 5 | it('returns the results of all promises in resolution order', async () => { 6 | const results = await a.set([ p('foo', 100), p('bar', 50), p('baz', 10) ]) 7 | assert.deepEqual(results, ['baz', 'bar', 'foo']) 8 | }) 9 | it('returns `count` results', async () => { 10 | const results = await a.set([ p('foo', 100), p('bar', 50), p('baz', 10) ], 2) 11 | assert.deepEqual(results, ['baz', 'bar']) 12 | }) 13 | it('throws if one of the first `count` results throws', async () => { 14 | try { 15 | const results = await a.set([ p('foo', 100), f(50), p('baz', 10) ], 2) 16 | throw new Error('should fail') 17 | } 18 | catch (err) { 19 | assert.equal(err.message, 'too many failures') 20 | } 21 | }) 22 | it("doesn't throw if promises throw after `count`", async () => { 23 | const results = await a.set([ p('foo', 100), f(50), p('baz', 10) ], 1) 24 | assert.deepEqual(results, ['baz']) 25 | }) 26 | it("doesn't throw if rejections < ignore", async () => { 27 | const results = await a.set([ p('foo', 100), f(50), p('baz', 10) ], 2, 1) 28 | assert.deepEqual(results, ['baz', 'foo']) 29 | }) 30 | it('throws if rejections > ignore', async () => { 31 | try { 32 | const results = await a.set([ f(120), p('foo', 100), f(50), f(10) ], 1, 1) 33 | throw new Error('should fail') 34 | } 35 | catch (err) { 36 | assert.equal(err.message, 'too many failures') 37 | } 38 | }) 39 | it('throws if rejections > list.length - count even if ignore is higher', async () => { 40 | try { 41 | const results = await a.set([ p('foo', 100), f(50), f(10) ], 2, 2) 42 | throw new Error('should fail') 43 | } 44 | catch (err) { 45 | assert.equal(err.message, 'too many failures') 46 | } 47 | }) 48 | }) 49 | 50 | function p(val, delay = 0) { 51 | return new Promise((resolve, reject) => { 52 | setTimeout(() => resolve(val), delay) 53 | }) 54 | } 55 | 56 | async function f(delay) { 57 | await a.delay(delay) 58 | throw new Error('fail') 59 | } 60 | -------------------------------------------------------------------------------- /test/single.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('single', () => { 5 | 6 | it('returns the promise that finishes first', async () => { 7 | const result = await a.single([prefix('foo', 100), prefix('bar', 20)]) 8 | assert.equal(result, 'p-bar') 9 | }) 10 | 11 | it('rejects if a promise rejects early', async () => { 12 | try { 13 | await a.single([prefix('foo', 100), fail(50)]) 14 | throw new Error('should fail') 15 | } 16 | catch (err) { 17 | assert.equal(err.message, 'too many failures') 18 | } 19 | }) 20 | 21 | it('resolves if a promise rejects late', async () => { 22 | const result = await a.single([prefix('foo', 50), fail(100)]) 23 | assert.equal(result, 'p-foo') 24 | }) 25 | 26 | it('ignores rejections if ignore > failures', async () => { 27 | const result = await a.single([ prefix('foo', 100), fail(50) ], 2) 28 | assert.equal(result, 'p-foo') 29 | }) 30 | 31 | it('throws if ignore is > failures but all promises fail', async () => { 32 | try { 33 | await a.single([ fail(100), fail(75), fail(50) ], Infinity) 34 | throw new Error('should fail') 35 | } 36 | catch (err) { 37 | assert.equal(err.message, 'too many failures') 38 | } 39 | }) 40 | 41 | async function prefix(val, ms=50) { 42 | await a.delay(ms) 43 | return `p-${val}` 44 | } 45 | 46 | async function fail(ms) { 47 | await a.delay(ms) 48 | throw new Error('fail') 49 | } 50 | }) 51 | -------------------------------------------------------------------------------- /test/success.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('success', () => { 5 | it('returns a value on success', async () => { 6 | const result = await a.success(succeed()) 7 | assert.equal(result, 'ok') 8 | }) 9 | it('returns undefined on error', async () => { 10 | const result = await a.success(fail()) 11 | assert.isUndefined(result) 12 | }) 13 | 14 | async function fail() { 15 | await a.delay(10) 16 | throw new Error('fail') 17 | } 18 | 19 | async function succeed() { 20 | await a.delay(10) 21 | return 'ok' 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /test/time.test.js: -------------------------------------------------------------------------------- 1 | const a = require('..') 2 | const assert = require('chai').assert 3 | 4 | describe('time', () => { 5 | it('waits until a date if a Date object is provided', async () => { 6 | const start = Date.now() 7 | const end = new Date(start + 100) 8 | await a.time(end) 9 | assert.closeTo(Date.now() - start, 100, 10) 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------