├── .gitignore ├── LICENSE ├── README-zh_CN.md ├── README.md ├── docs ├── .vuepress │ └── config.js ├── README.md ├── dist │ ├── 404.html │ ├── assets │ │ ├── css │ │ │ └── 0.styles.6467ecbe.css │ │ ├── img │ │ │ └── search.83621669.svg │ │ └── js │ │ │ ├── 2.3badc248.js │ │ │ ├── 3.039c8eb4.js │ │ │ ├── 4.d46c551b.js │ │ │ ├── 5.923b9e8f.js │ │ │ ├── 6.399ce124.js │ │ │ ├── 7.736b3428.js │ │ │ ├── 8.35838317.js │ │ │ └── app.11798775.js │ ├── guide │ │ ├── advanced.html │ │ ├── example.html │ │ ├── getting-started.html │ │ └── index.html │ └── index.html └── guide │ ├── README.md │ ├── advanced.md │ ├── example.md │ └── getting-started.md ├── lib ├── index.ts ├── public_api.ts └── src │ ├── backend.ts │ ├── client.ts │ ├── cookie.ts │ ├── handler.ts │ ├── headers.ts │ ├── interceptor.ts │ ├── jsonp.ts │ ├── params.ts │ ├── request.ts │ ├── response.ts │ ├── rjax.ts │ ├── xhr.ts │ └── xsrf.ts ├── package.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /dist/ 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2024 ppjjzz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 | # rjax 2 | 3 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](/LICENSE) 4 | 5 | [English](./README.md) | 简体中文 6 | 7 | ## ✨ 特性 8 | 9 | - 移植优秀的@angular/common/http模块。 10 | - 基于rxjs的响应式请求库。 11 | - 使用 TypeScript 构建,提供完整的类型定义文件。 12 | - 功能强大又简单易用。 13 | 14 | ## 📦 安装 15 | 16 | ```bash 17 | yarn add rjax # 或者:npm install rjax --save 18 | ``` 19 | 20 | ## 🔨 示例 21 | 22 | ```js 23 | import { Rjax } from 'rjax'; 24 | const rjax = new Rjax(); 25 | rjax.get(`/user/12345`).subscribe(response => { 26 | // 请求成功回调 27 | console.log(response); 28 | }, error => { 29 | // 请求失败回调 30 | console.log(error); 31 | }); 32 | ``` 33 | 34 | ## 文档 35 | API 文档及示例 [链接](https://ppjjzz.github.io/rjax/dist/index.html) 36 | 37 | ## 作者 38 | 39 | **rjax** © [ppjjzz](https://github.com/ppjjzz), Released under the [MIT](./LICENSE) License.
40 | Authored and maintained by ppjjzz. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rjax 2 | 3 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](/LICENSE) 4 | 5 | English | [简体中文](./README-zh_CN.md) 6 | 7 | ## ✨ Features 8 | 9 | - Porting the excellent @angular/common/http module. 10 | - Responsive ajax library based on rxjs. 11 | - Written in TypeScript with predictable static types. 12 | - Powerful and easy to use. 13 | 14 | ## 📦 Install 15 | 16 | ```bash 17 | yarn add rjax # or: npm install rjax --save 18 | ``` 19 | 20 | ## 🔨 Usage 21 | 22 | ```js 23 | import { Rjax } from 'rjax'; 24 | const rjax = new Rjax(); 25 | rjax.get(`/user/12345`).subscribe(response => { 26 | // Succeed Callback 27 | console.log(response); 28 | }, error => { 29 | // Error Callback 30 | console.log(error); 31 | }); 32 | ``` 33 | 34 | ## Docs 35 | API document and example [link](https://ppjjzz.github.io/rjax/dist/index.html) 36 | 37 | ## Author 38 | 39 | **rjax** © [ppjjzz](https://github.com/ppjjzz), Released under the [MIT](./LICENSE) License.
40 | Authored and maintained by ppjjzz. -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Rjax', 3 | description: 'base on rxjs awesome ajax library', 4 | base: '/rjax/dist/', 5 | dest: './docs/dist', 6 | themeConfig: { 7 | repo: 'ppjjzz/rjax', 8 | nav: [ 9 | { text: '指南', link: '/guide/' }, 10 | ], 11 | sidebar: { 12 | '/guide/': [{ 13 | title: '指南', 14 | collapsable: false, 15 | children: [ 16 | '', 17 | 'getting-started', 18 | 'advanced', 19 | 'example' 20 | ] 21 | }] 22 | } 23 | }, 24 | 25 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: Rjax 4 | tagline: 基于Rxjs的响应式AJAX请求库 5 | actionText: 快速上手 → 6 | actionLink: /guide/ 7 | features: 8 | - title: 功能强大 9 | details: 移植于@angular/common/http模块,可享有几乎同样的HttpClient功能。 10 | - title: Rxjs驱动 11 | details: 享受 Rxjs的响应式和函数式编程体验。 12 | - title: 使用 TypeScript 构建 13 | details: 提供完整的类型定义文件和IDE完美提示。 14 | footer: MIT Licensed | Copyright © 2024 ppjjzz 15 | --- -------------------------------------------------------------------------------- /docs/dist/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rjax 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

404

Looks like we've got some broken links.
Take me home.
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/dist/assets/css/0.styles.6467ecbe.css: -------------------------------------------------------------------------------- 1 | .home{padding:3.6rem 2rem 0;max-width:960px;margin:0 auto}.home .hero{text-align:center}.home .hero img{max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.8rem auto}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:#6a8bad}.home .hero .action-button{display:inline-block;font-size:1.2rem;color:#fff;background-color:#3eaf7c;padding:.8rem 1.6rem;border-radius:4px;transition:background-color .1s ease;box-sizing:border-box;border-bottom:1px solid #389d70}.home .hero .action-button:hover{background-color:#4abf8a}.home .features{border-top:1px solid #eaecef;padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:#3a5169}.home .feature p{color:#4e6e8e}.home .footer{padding:2.5rem;border-top:1px solid #eaecef;text-align:center;color:#4e6e8e}@media (max-width:719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width:419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.badge[data-v-099ab69c]{display:inline-block;font-size:14px;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:#fff;margin-right:5px;background-color:#42b983}.badge.middle[data-v-099ab69c]{vertical-align:middle}.badge.top[data-v-099ab69c]{vertical-align:top}.badge.green[data-v-099ab69c],.badge.tip[data-v-099ab69c]{background-color:#42b983}.badge.error[data-v-099ab69c]{background-color:#da5961}.badge.warn[data-v-099ab69c],.badge.warning[data-v-099ab69c],.badge.yellow[data-v-099ab69c]{background-color:#e7c000}.search-box{display:inline-block;position:relative;margin-right:1rem}.search-box input{cursor:text;width:10rem;color:#4e6e8e;display:inline-block;border:1px solid #cfd4db;border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:none;transition:all .2s ease;background:#fff url(/rjax/dist/assets/img/search.83621669.svg) .6rem .5rem no-repeat;background-size:1rem}.search-box input:focus{cursor:auto;border-color:#3eaf7c}.search-box .suggestions{background:#fff;width:20rem;position:absolute;top:1.5rem;border:1px solid #cfd4db;border-radius:6px;padding:.4rem;list-style-type:none}.search-box .suggestions.align-right{right:0}.search-box .suggestion{line-height:1.4;padding:.4rem .6rem;border-radius:4px;cursor:pointer}.search-box .suggestion a{white-space:normal;color:#5d82a6}.search-box .suggestion a .page-title{font-weight:600}.search-box .suggestion a .header{font-size:.9em;margin-left:.25em}.search-box .suggestion.focused{background-color:#f3f4f5}.search-box .suggestion.focused a{color:#3eaf7c}@media (max-width:959px){.search-box input{cursor:pointer;width:0;border-color:transparent;position:relative}.search-box input:focus{cursor:text;left:0;width:10rem}}@media (max-width:959px) and (min-width:719px){.search-box .suggestions{left:0}}@media (max-width:719px){.search-box{margin-right:0}.search-box input{left:1rem}.search-box .suggestions{right:0}}@media (max-width:419px){.search-box .suggestions{width:calc(100vw - 4rem)}.search-box input:focus{width:8rem}}.dropdown-enter,.dropdown-leave-to{height:0!important}.dropdown-wrapper{cursor:pointer}.dropdown-wrapper .dropdown-title{display:block}.dropdown-wrapper .dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.dropdown-wrapper .nav-dropdown .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .nav-dropdown .dropdown-item h4{margin:.45rem 0 0;border-top:1px solid #eee;padding:.45rem 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper{padding:0;list-style:none}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper .dropdown-subitem{font-size:.9em}.dropdown-wrapper .nav-dropdown .dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active,.dropdown-wrapper .nav-dropdown .dropdown-item a:hover{color:#3eaf7c}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{content:"";width:0;height:0;border-left:5px solid #3eaf7c;border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.dropdown-wrapper .nav-dropdown .dropdown-item:first-child h4{margin-top:0;padding-top:0;border-top:0}@media (max-width:719px){.dropdown-wrapper.open .dropdown-title{margin-bottom:.5rem}.dropdown-wrapper .nav-dropdown{transition:height .1s ease-out;overflow:hidden}.dropdown-wrapper .nav-dropdown .dropdown-item h4{border-top:0;margin-top:0;padding-top:0}.dropdown-wrapper .nav-dropdown .dropdown-item>a,.dropdown-wrapper .nav-dropdown .dropdown-item h4{font-size:15px;line-height:2rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem{font-size:14px;padding-left:1rem}}@media (min-width:719px){.dropdown-wrapper{height:1.8rem}.dropdown-wrapper:hover .nav-dropdown{display:block!important}.dropdown-wrapper .dropdown-title .arrow{border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #ccc;border-bottom:0}.dropdown-wrapper .nav-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:#fff;padding:.6rem 0;border:1px solid #ddd;border-bottom-color:#ccc;text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}}.nav-links{display:inline-block}.nav-links a{line-height:1.4rem;color:inherit}.nav-links a.router-link-active,.nav-links a:hover{color:#3eaf7c}.nav-links .nav-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:2rem}.nav-links .nav-item:first-child{margin-left:0}.nav-links .repo-link{margin-left:1.5rem}@media (max-width:719px){.nav-links .nav-item,.nav-links .repo-link{margin-left:0}}@media (min-width:719px){.nav-links a.router-link-active,.nav-links a:hover{color:#2c3e50}.nav-item>a:not(.external).router-link-active,.nav-item>a:not(.external):hover{margin-bottom:-2px;border-bottom:2px solid #46bd87}}.navbar{padding:.7rem 1.5rem;line-height:2.2rem;position:relative}.navbar a,.navbar img,.navbar span{display:inline-block}.navbar .logo{height:2.2rem;min-width:2.2rem;margin-right:.8rem;vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:#2c3e50;position:relative}.navbar .links{padding-left:1.5rem;box-sizing:border-box;background-color:#fff;white-space:nowrap;font-size:.9rem;position:absolute;right:1.5rem;top:.7rem;display:flex}.navbar .links .search-box{flex:0 0 auto;vertical-align:top}.navbar .links .nav-links{flex:1}@media (max-width:719px){.navbar{padding-left:4rem}.navbar .can-hide{display:none}.navbar .links{padding-left:1.5rem}}.page-edit,.page-nav{max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.page-edit,.page-nav{padding:2rem}}@media (max-width:419px){.page-edit,.page-nav{padding:1.5rem}}.page{padding-bottom:2rem}.page-edit{padding-top:1rem;padding-bottom:1rem;overflow:auto}.page-edit .edit-link{display:inline-block}.page-edit .edit-link a{color:#4e6e8e;margin-right:.25rem}.page-edit .last-updated{float:right;font-size:.9em}.page-edit .last-updated .prefix{font-weight:500;color:#4e6e8e}.page-edit .last-updated .time{font-weight:400;color:#aaa}.page-nav{padding-top:1rem;padding-bottom:0}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid #eaecef;padding-top:1rem;overflow:auto}.page-nav .next{float:right}@media (max-width:719px){.page-edit .edit-link{margin-bottom:.5rem}.page-edit .last-updated{font-size:.8em;float:none;text-align:left}}.sidebar-button{display:none;width:1.25rem;height:1.25rem;position:absolute;padding:.6rem;top:.6rem;left:1rem}.sidebar-button .icon{display:block;width:1.25rem;height:1.25rem}@media (max-width:719px){.sidebar-button{display:block}}.sidebar-group:not(.first){margin-top:1em}.sidebar-group .sidebar-group{padding-left:.5em}.sidebar-group:not(.collapsable) .sidebar-heading{cursor:auto;color:inherit}.sidebar-heading{color:#999;transition:color .15s ease;cursor:pointer;font-size:1.1em;font-weight:700;padding:0 1.5rem;margin-top:0;margin-bottom:.5rem}.sidebar-heading.open,.sidebar-heading:hover{color:inherit}.sidebar-heading .arrow{position:relative;top:-.12em;left:.5em}.sidebar-heading:.open .arrow{top:-.18em}.sidebar-group-items{transition:height .1s ease-out;overflow:hidden}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .nav-links{display:none;border-bottom:1px solid #eaecef;padding:.5rem 0 .75rem 0}.sidebar .nav-links a{font-weight:600}.sidebar .nav-links .nav-item,.sidebar .nav-links .repo-link{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar .sidebar-links{padding:1.5rem 0}@media (max-width:719px){.sidebar .nav-links{display:block}.sidebar .nav-links .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{top:calc(1rem - 2px)}.sidebar .sidebar-links{padding:1rem 0}}.sw-update-popup{position:fixed;right:1em;bottom:1em;padding:1em;border:1px solid #3eaf7c;border-radius:3px;background:#fff;box-shadow:0 4px 16px rgba(0,0,0,.5);text-align:center}.sw-update-popup button{margin-top:.5em;padding:.25em 2em}.sw-update-popup-enter-active,.sw-update-popup-leave-active{transition:opacity .3s,transform .3s}.sw-update-popup-enter,.sw-update-popup-leave-to{opacity:0;transform:translateY(50%) scale(.5)}code[class*=language-],pre[class*=language-]{color:#ccc;background:none;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}#nprogress{pointer-events:none}#nprogress .bar{background:#3eaf7c;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #3eaf7c,0 0 5px #3eaf7c;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#3eaf7c;border-left-color:#3eaf7c;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.content code{color:#476582;padding:.25rem .5rem;margin:0;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.content pre,.content pre[class*=language-]{line-height:1.4;padding:1.25rem 1.5rem;margin:.85rem 0;background-color:#282c34;border-radius:6px;overflow:auto}.content pre[class*=language-] code,.content pre code{color:#fff;padding:0;background-color:transparent;border-radius:0}div[class*=language-]{position:relative;background-color:#282c34;border-radius:6px}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.4}div[class*=language-] .highlight-lines .highlighted{background-color:rgba(0,0,0,.66)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:transparent;position:relative;z-index:1}div[class*=language-]:before{position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:hsla(0,0%,100%,.4)}div[class*=language-]:not(.line-numbers-mode) .line-numbers-wrapper{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlighted{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlighted:before{content:" ";position:absolute;z-index:3;left:0;top:0;display:block;width:3.5rem;height:100%;background-color:rgba(0,0,0,.66)}div[class*=language-].line-numbers-mode pre{padding-left:4.5rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers-wrapper{position:absolute;top:0;width:3.5rem;text-align:center;color:hsla(0,0%,100%,.3);padding:1.25rem 0;line-height:1.4}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number,div[class*=language-].line-numbers-mode .line-numbers-wrapper br{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number{position:relative;z-index:4;font-size:.85em}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;z-index:2;top:0;left:0;width:3.5rem;height:100%;border-radius:6px 0 0 6px;border-right:1px solid rgba(0,0,0,.66);background-color:#282c34}div[class~=language-js]:before{content:"js"}div[class~=language-ts]:before{content:"ts"}div[class~=language-html]:before{content:"html"}div[class~=language-md]:before{content:"md"}div[class~=language-vue]:before{content:"vue"}div[class~=language-css]:before{content:"css"}div[class~=language-sass]:before{content:"sass"}div[class~=language-scss]:before{content:"scss"}div[class~=language-less]:before{content:"less"}div[class~=language-stylus]:before{content:"stylus"}div[class~=language-go]:before{content:"go"}div[class~=language-java]:before{content:"java"}div[class~=language-c]:before{content:"c"}div[class~=language-sh]:before{content:"sh"}div[class~=language-yaml]:before{content:"yaml"}div[class~=language-py]:before{content:"py"}div[class~=language-javascript]:before{content:"js"}div[class~=language-typescript]:before{content:"ts"}div[class~=language-markup]:before{content:"html"}div[class~=language-markdown]:before{content:"md"}div[class~=language-json]:before{content:"json"}div[class~=language-ruby]:before{content:"rb"}div[class~=language-python]:before{content:"py"}div[class~=language-bash]:before{content:"sh"}.custom-block .custom-block-title{font-weight:600;margin-bottom:-.4rem}.custom-block.danger,.custom-block.tip,.custom-block.warning{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-block.tip{background-color:#f3f5f7;border-color:#42b983}.custom-block.warning{background-color:rgba(255,229,100,.3);border-color:#e7c000;color:#6b5900}.custom-block.warning .custom-block-title{color:#b29400}.custom-block.warning a{color:#2c3e50}.custom-block.danger{background-color:#ffe6e6;border-color:#c00;color:#4d0000}.custom-block.danger .custom-block-title{color:#900}.custom-block.danger a{color:#2c3e50}.arrow{display:inline-block;width:0;height:0}.arrow.up{border-bottom:6px solid #ccc}.arrow.down,.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent}.arrow.down{border-top:6px solid #ccc}.arrow.right{border-left:6px solid #ccc}.arrow.left,.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent}.arrow.left{border-right:6px solid #ccc}.content:not(.custom){max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.content:not(.custom){padding:2rem}}@media (max-width:419px){.content:not(.custom){padding:1.5rem}}.table-of-contents .badge{vertical-align:middle}body,html{padding:0;margin:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:16px;color:#2c3e50}.page{padding-left:20rem}.navbar{z-index:20;right:0;height:3.6rem;background-color:#fff;box-sizing:border-box;border-bottom:1px solid #eaecef}.navbar,.sidebar-mask{position:fixed;top:0;left:0}.sidebar-mask{z-index:9;width:100vw;height:100vh;display:none}.sidebar{font-size:15px;background-color:#fff;width:20rem;position:fixed;z-index:10;margin:0;top:3.6rem;left:0;bottom:0;box-sizing:border-box;border-right:1px solid #eaecef;overflow-y:auto}.content:not(.custom)>:first-child{margin-top:3.6rem}.content:not(.custom) a:hover{text-decoration:underline}.content:not(.custom) p.demo{padding:1rem 1.5rem;border:1px solid #ddd;border-radius:4px}.content:not(.custom) img{max-width:100%}.content.custom{padding:0;margin:0}.content.custom img{max-width:100%}a{font-weight:500;text-decoration:none}a,p a code{color:#3eaf7c}p a code{font-weight:400}kbd{background:#eee;border:.15rem solid #ddd;border-bottom:.25rem solid #ddd;border-radius:.15rem;padding:0 .15em}blockquote{font-size:1.2rem;color:#999;border-left:.25rem solid #dfe2e5;margin-left:0;padding-left:1rem}ol,ul{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25}.content:not(.custom)>h1,.content:not(.custom)>h2,.content:not(.custom)>h3,.content:not(.custom)>h4,.content:not(.custom)>h5,.content:not(.custom)>h6{margin-top:-3.1rem;padding-top:4.6rem;margin-bottom:0}.content:not(.custom)>h1:first-child,.content:not(.custom)>h2:first-child,.content:not(.custom)>h3:first-child,.content:not(.custom)>h4:first-child,.content:not(.custom)>h5:first-child,.content:not(.custom)>h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.content:not(.custom)>h1:first-child+.custom-block,.content:not(.custom)>h1:first-child+p,.content:not(.custom)>h1:first-child+pre,.content:not(.custom)>h2:first-child+.custom-block,.content:not(.custom)>h2:first-child+p,.content:not(.custom)>h2:first-child+pre,.content:not(.custom)>h3:first-child+.custom-block,.content:not(.custom)>h3:first-child+p,.content:not(.custom)>h3:first-child+pre,.content:not(.custom)>h4:first-child+.custom-block,.content:not(.custom)>h4:first-child+p,.content:not(.custom)>h4:first-child+pre,.content:not(.custom)>h5:first-child+.custom-block,.content:not(.custom)>h5:first-child+p,.content:not(.custom)>h5:first-child+pre,.content:not(.custom)>h6:first-child+.custom-block,.content:not(.custom)>h6:first-child+p,.content:not(.custom)>h6:first-child+pre{margin-top:2rem}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid #eaecef}h3{font-size:1.35rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0}a.header-anchor:hover{text-decoration:none}.line-number,code,kbd{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}ol,p,ul{line-height:1.7}hr{border:0;border-top:1px solid #eaecef}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto}tr{border-top:1px solid #dfe2e5}tr:nth-child(2n){background-color:#f6f8fa}td,th{border:1px solid #dfe2e5;padding:.6em 1em}.custom-layout{padding-top:3.6rem}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.no-navbar .content:not(.custom)>h1,.theme-container.no-navbar h2,.theme-container.no-navbar h3,.theme-container.no-navbar h4,.theme-container.no-navbar h5,.theme-container.no-navbar h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .sidebar{top:0}.theme-container.no-navbar .custom-layout{padding-top:0}@media (min-width:720px){.theme-container.no-sidebar .sidebar{display:none}.theme-container.no-sidebar .page{padding-left:0}}@media (max-width:959px){.sidebar{font-size:15px;width:16.4rem}.page{padding-left:16.4rem}}@media (max-width:719px){.sidebar{top:0;padding-top:3.6rem;transform:translateX(-100%);transition:transform .2s ease}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translateX(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width:419px){h1{font-size:1.9rem}.content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.icon.outbound{color:#aaa;display:inline-block}.sidebar .sidebar-sub-headers{padding-left:1rem;font-size:.95em}a.sidebar-link{font-weight:400;display:inline-block;color:#2c3e50;border-left:.25rem solid transparent;padding:.35rem 1rem .35rem 1.25rem;line-height:1.4;width:100%;box-sizing:border-box}a.sidebar-link:hover{color:#3eaf7c}a.sidebar-link.active{font-weight:600;color:#3eaf7c;border-left-color:#3eaf7c}.sidebar-group a.sidebar-link{padding-left:2rem}.sidebar-sub-headers a.sidebar-link{padding-top:.25rem;padding-bottom:.25rem;border-left:none}.sidebar-sub-headers a.sidebar-link.active{font-weight:500} -------------------------------------------------------------------------------- /docs/dist/assets/img/search.83621669.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/dist/assets/js/2.3badc248.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[2],{164:function(t,e,n){},165:function(t,e,n){"use strict";var a=n(164);n.n(a).a},167:function(t,e,n){"use strict";n.r(e);var a={functional:!0,props:{type:{type:String,default:"tip"},text:String,vertical:{type:String,default:"top"}},render:function(t,e){var n=e.props,a=e.slots;return t("span",{class:["badge",n.type,n.vertical]},n.text||a().default)}},i=(n(165),n(0)),o=Object(i.a)(a,void 0,void 0,!1,null,"099ab69c",null);o.options.__file="Badge.vue";e.default=o.exports}}]); -------------------------------------------------------------------------------- /docs/dist/assets/js/3.039c8eb4.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[3],{169:function(t,n,e){"use strict";e.r(n);var s=e(0),i=Object(s.a)({},function(){var t=this.$createElement;return(this._self._c||t)("div",{staticClass:"content"})},[],!1,null,null,null);i.options.__file="README.md";n.default=i.exports}}]); -------------------------------------------------------------------------------- /docs/dist/assets/js/4.d46c551b.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[4],{170:function(a,t,r){"use strict";r.r(t);var e=r(0),i=Object(e.a)({},function(){this.$createElement;this._self._c;return this._m(0)},[function(){var a=this,t=a.$createElement,r=a._self._c||t;return r("div",{staticClass:"content"},[r("h1",{attrs:{id:"介绍"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#介绍","aria-hidden":"true"}},[a._v("#")]),a._v(" 介绍")]),a._v(" "),r("h2",{attrs:{id:"特性"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#特性","aria-hidden":"true"}},[a._v("#")]),a._v(" 特性")]),a._v(" "),r("ul",[r("li",[a._v("移植优秀的@angular/common/http模块。")]),a._v(" "),r("li",[a._v("兼容非angular项目")]),a._v(" "),r("li",[a._v("基于rxjs的响应式请求库。")]),a._v(" "),r("li",[a._v("使用 TypeScript 构建,提供完整的类型定义文件。")]),a._v(" "),r("li",[a._v("功能强大又简单易用。")]),a._v(" "),r("li",[a._v("\b可自定义多个请求响应的拦截器")])]),a._v(" "),r("h2",{attrs:{id:"为什么不是"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#为什么不是","aria-hidden":"true"}},[a._v("#")]),a._v(" 为什么不是...?")]),a._v(" "),r("h3",{attrs:{id:"fetch"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#fetch","aria-hidden":"true"}},[a._v("#")]),a._v(" fetch")]),a._v(" "),r("ul",[r("li",[a._v("fetch浏览器原生内置API")]),a._v(" "),r("li",[a._v("优点:免引入第三方依赖,基于Promise")]),a._v(" "),r("li",[a._v("缺点:API过于简单,没有提供拦截器功能,需要手动序列化和反序列化,一般需要经过封装后才可以使用,兼容性问题")])]),a._v(" "),r("h3",{attrs:{id:"axios"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#axios","aria-hidden":"true"}},[a._v("#")]),a._v(" axios")]),a._v(" "),r("ul",[r("li",[a._v("目前最流行的ajax请求库之一")]),a._v(" "),r("li",[a._v("优点:\b可自定义的配置多,提供拦截器,兼容性好,体积小,基于Promise")]),a._v(" "),r("li",[a._v("缺点:cancel的API比较难用,无法应对复杂的请求场景,不支持jsonp")])]),a._v(" "),r("h3",{attrs:{id:"jquery"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#jquery","aria-hidden":"true"}},[a._v("#")]),a._v(" JQuery")]),a._v(" "),r("ul",[r("li",[a._v("老牌的前端类库")]),a._v(" "),r("li",[a._v("优点:兼容性好")]),a._v(" "),r("li",[a._v("缺点:体积大,没有提供拦截器功能,请求配置优点复杂")])])])}],!1,null,null,null);i.options.__file="README.md";t.default=i.exports}}]); -------------------------------------------------------------------------------- /docs/dist/assets/js/6.399ce124.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[6],{171:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){this.$createElement;this._self._c;return this._m(0)},[function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"content"},[a("h1",{attrs:{id:"使用示例"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用示例","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用示例")]),t._v(" "),a("h2",{attrs:{id:"并发请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#并发请求","aria-hidden":"true"}},[t._v("#")]),t._v(" 并发请求")]),t._v(" "),a("p",[t._v("\b并发多个请求,等待所有请求都结束后才执行回调相当于 "),a("strong",[t._v("Promise.all()")])]),t._v(" "),a("div",{staticClass:"language-js extra-class"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" Rjax "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'rjax'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" combineLatest "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'rxjs'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 创建实例")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" rjax "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("new")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Rjax")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" p1$ "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" rjax"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`/user/12345`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" p2$ "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" rjax"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`/user/123456`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("combineLatest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("p1$"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" p2$"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("subscribe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("res1"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" res2"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 请求成功回调")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("res1"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// p1$ 的请求结果")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("res2"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// p2$ 的请求结果")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" error "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 请求失败回调")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("error"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])])]),a("h2",{attrs:{id:"多数据源合并"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#多数据源合并","aria-hidden":"true"}},[t._v("#")]),t._v(" \b多数据源合并")]),t._v(" "),a("p",[t._v("例如某个图表的数据来源于websocket和ajax手动请求,返回格式是一样的,可以将这两个数据流进行合并创建新的\bObservable,组件只要订阅合并后的数据流而不关心数据的来源")]),t._v(" "),a("div",{staticClass:"language-js extra-class"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" Rjax "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'rjax'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" merge "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'rxjs'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" webSocket "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'rxjs/webSocket'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" rjax "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("new")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Rjax")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ws$ "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("webSocket")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'wss://echo.websocket.org'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// websocket")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ajax$ "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" rjax"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`/user/12345`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// ajax")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 合并数据流")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("merge")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("ws$"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ajax$"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("subscribe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("res "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" err "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'请求出错'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])])])])}],!1,null,null,null);e.options.__file="example.md";s.default=e.exports}}]); -------------------------------------------------------------------------------- /docs/dist/assets/js/8.35838317.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[8],{166:function(n,w,o){}}]); -------------------------------------------------------------------------------- /docs/dist/guide/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 使用示例 | Rjax 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

使用示例

并发请求

并发多个请求,等待所有请求都结束后才执行回调相当于 Promise.all()

import { Rjax } from 'rjax';
19 | import { combineLatest } from 'rxjs';
20 | 
21 | // 创建实例
22 | const rjax = new Rjax();
23 | 
24 | const p1$ = rjax.get(`/user/12345`);
25 | const p2$ = rjax.get(`/user/123456`);
26 | 
27 | combineLatest(p1$, p2$).subscribe(([res1, res2]) => {
28 |     // 请求成功回调
29 |     console.log(res1); // p1$ 的请求结果
30 |     console.log(res2); // p2$ 的请求结果
31 | }, error => {
32 |     // 请求失败回调
33 |     console.log(error);
34 | });
35 | 

多数据源合并

例如某个图表的数据来源于websocket和ajax手动请求,返回格式是一样的,可以将这两个数据流进行合并创建新的Observable,组件只要订阅合并后的数据流而不关心数据的来源

import { Rjax } from 'rjax';
36 | import { merge } from 'rxjs';
37 | import { webSocket } from 'rxjs/webSocket';
38 | 
39 | const rjax = new Rjax();
40 | 
41 | const ws$ = webSocket('wss://echo.websocket.org'); // websocket
42 | const ajax$ = rjax.get(`/user/12345`); // ajax
43 | 
44 | // 合并数据流
45 | merge(ws$, ajax$).subscribe(res => {
46 |     console.log(res)
47 | }, err => console.log('请求出错'));
48 | 
53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/dist/guide/getting-started.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 快速上手 | Rjax 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

快速上手

安装

yarn add rjax # 或者:npm install rjax --save
19 | 

使用

import { Rjax } from 'rjax';
20 | 
21 | // 创建实例
22 | const rjax = new Rjax({
23 |   baseURL: 'https://some-domain.com/api/', // 设置请求基路径,可选
24 |   timeout: 1000, // 设置请求超时时间,可选
25 |   interceptors: [] // 设置请求响应拦截器,可设置多组,可选
26 |   xsrfCookieName: 'XSRF-TOKEN', // 是用作 xsrf token 的值的cookie的名称,默认'XSRF-TOKEN',可选
27 |   xsrfHeaderName: 'X-XSRF-TOKEN', // 是承载 xsrf token 的值的 HTTP 头的名称,默认'X-XSRF-TOKEN',可选
28 |   headers: {}, // 添加统一的headers,默认{},可选
29 |   withCredentials: false, // 表示跨域请求时是否需要使用凭证,默认false,可选
30 |   jsonp: false, // 是否添加jsonp请求功能,默认false,可选
31 | });
32 | 
33 | // 发起GET请求
34 | rjax.get(`/user/12345`).subscribe(response => {
35 |     // 请求成功回调
36 |     console.log(response);
37 | }, error => {
38 |     // 请求失败回调
39 |     console.log(error);
40 | });
41 | 

以下是可用的实例方法

class HttpClient {
42 |   request(first: string | HttpRequest<any>, url?: string, options: { body?: any; headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | ... 2 more ... | "json"; withCredentials?: boolean; } = {}): Observable<any>
43 |   delete(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable<any>
44 |   get(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable<any>
45 |   head(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable<any>
46 |   jsonp<T>(url: string, callbackParam: string): Observable<T>
47 |   options(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable<any>
48 |   patch(url: string, body: any, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable<any>
49 |   post(url: string, body: any, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable<any>
50 |   put(url: string, body: any, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable<any>
51 | }
52 | 
61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/dist/guide/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 介绍 | Rjax 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

介绍

特性

  • 移植优秀的@angular/common/http模块。
  • 兼容非angular项目
  • 基于rxjs的响应式请求库。
  • 使用 TypeScript 构建,提供完整的类型定义文件。
  • 功能强大又简单易用。
  • 可自定义多个请求响应的拦截器

为什么不是...?

fetch

  • fetch浏览器原生内置API
  • 优点:免引入第三方依赖,基于Promise
  • 缺点:API过于简单,没有提供拦截器功能,需要手动序列化和反序列化,一般需要经过封装后才可以使用,兼容性问题

axios

  • 目前最流行的ajax请求库之一
  • 优点:可自定义的配置多,提供拦截器,兼容性好,体积小,基于Promise
  • 缺点:cancel的API比较难用,无法应对复杂的请求场景,不支持jsonp

JQuery

  • 老牌的前端类库
  • 优点:兼容性好
  • 缺点:体积大,没有提供拦截器功能,请求配置优点复杂
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rjax 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Rjax

19 | 基于Rxjs的响应式AJAX请求库 20 |

快速上手 →

功能强大

移植于@angular/common/http模块,可享有几乎同样的HttpClient功能。

Rxjs驱动

享受 Rxjs的响应式和函数式编程体验。

使用 TypeScript 构建

提供完整的类型定义文件和IDE完美提示。

23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | ## 特性 4 | 5 | - 移植优秀的@angular/common/http模块。 6 | - 兼容非angular项目 7 | - 基于rxjs的响应式请求库。 8 | - 使用 TypeScript 构建,提供完整的类型定义文件。 9 | - 功能强大又简单易用。 10 | - 可自定义多个请求响应的拦截器 11 | 12 | ## 为什么不是...? 13 | 14 | ### fetch 15 | - fetch浏览器原生内置API 16 | - 优点:免引入第三方依赖,基于Promise 17 | - 缺点:API过于简单,没有提供拦截器功能,需要手动序列化和反序列化,一般需要经过封装后才可以使用,兼容性问题 18 | 19 | ### axios 20 | - 目前最流行的ajax请求库之一 21 | - 优点:可自定义的配置多,提供拦截器,兼容性好,体积小,基于Promise 22 | - 缺点:cancel的API比较难用,无法应对复杂的请求场景,不支持jsonp 23 | 24 | ### JQuery 25 | - 老牌的前端类库 26 | - 优点:兼容性好 27 | - 缺点:体积大,没有提供拦截器功能,请求配置优点复杂 -------------------------------------------------------------------------------- /docs/guide/advanced.md: -------------------------------------------------------------------------------- 1 | # 进阶 2 | 3 | ## 拦截请求和响应 4 | 使用拦截机制,你可以声明一些拦截器,用它们监视和转换从应用发送到服务器的 HTTP 请求。 拦截器还可以用监视和转换从服务器返回到本应用的那些响应。 多个选择器会构成一个“请求/响应处理器”的双向链表。 5 | 6 | 拦截器可以用一种常规的、标准的方式对每一次 HTTP 的请求/响应任务执行从认证到记日志等很多种隐式任务。 7 | 8 | 如果没有拦截机制,那么开发人员将不得不对每次 HttpClient 调用显式实现这些任务。 9 | 10 | ### 编写拦截器 11 | 要实现拦截器,就要实现一个实现了 HttpInterceptor 接口中的 intercept() 方法的类。 12 | 13 | ```js 14 | import { HttpResponse } from 'rjax'; 15 | import { Observable , throwError as _throw } from 'rxjs'; 16 | import { tap, mergeMap, finalize, catchError } from 'rxjs/operators'; 17 | import { Rjax } from 'rjax'; 18 | 19 | class CustomInterceptor { 20 | intercept(req, next) { 21 | console.log('拦截请求'); 22 | // 一定要用clone的方法进行拦截修改,为了保持请求的不可变性!!!! 23 | const newReq = req.clone({ 24 | url: req.url.replace('http://', 'https://'), // 修改请求的url 25 | body: {...req.body, name: req.body.name.trim()} // 修改请求体 26 | headers: req.headers.set('Authorization', 'authToken'), // 添加请求头 27 | }); 28 | return next.handle(newReq).pipe( 29 | tap(x => console.log('拦截响应', x)), 30 | , mergeMap(event => { 31 | // 这里可根据后台接口约定自行判断 32 | if (event instanceof HttpResponse && (event.status !== 200 || !event.body.success)) { 33 | return Observable.create(observer => observer.error(event)); 34 | } 35 | return Observable.create(observer => observer.next(event)); 36 | }) 37 | , catchError(res => { 38 | switch (res.status) { 39 | case 401: 40 | // 拦截到401错误 41 | break; 42 | case 200: 43 | // 业务层级错误处理 44 | break; 45 | case 404: 46 | 47 | break; 48 | case 500: 49 | 50 | break; 51 | default: 52 | 53 | break; 54 | } 55 | return _throw(res); // 将错误信息抛给下个拦截器或者请求调用方 56 | }), finalize(() => { 57 | // 无论成功或者失败都会执行 58 | // 可以记录日志等等 59 | } 60 | ) 61 | ); 62 | } 63 | } 64 | 65 | // 创建实例 66 | const rjax = new Rjax({ 67 | interceptors: [new CustomInterceptor()] // 设置请求响应拦截器,可设置多组 68 | }); 69 | ``` 70 | 71 | ### 拦截器的顺序 72 | Rjax 会按照你提供它们的顺序应用这些拦截器。 如果你提供拦截器的顺序是先 A,再 B,再 C,那么请求阶段的执行顺序就是 A->B->C,而响应阶段的执行顺序则是 C->B->A。 73 | 74 | 以后你就再也不能修改这些顺序或移除某些拦截器了。 如果你需要动态启用或禁用某个拦截器,那就要在那个拦截器中自行实现这个功能。 75 | 76 | ## 请求的防抖(debounce) 77 | 当用户在搜索框中输入名字时进行远程检索 78 | 79 | 如果每次击键都发送一次请求就太昂贵了。 最好能等到用户停止输入时才发送请求。 使用 RxJS 的操作符就能轻易实现它,参见下面的代码片段: 80 | ```jsx 81 | import { Subject } from 'rxjs'; 82 | import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; 83 | import { Rjax } from 'rjax'; 84 | export default class InputSearch extends Component { 85 | subject$ = new Subject(); 86 | state = { 87 | data: [] 88 | }; 89 | rjax = new Rjax(); 90 | componentDidMount() { 91 | this.subjectRef = this.subject.pipe( 92 | debounceTime(500), 93 | distinctUntilChanged(), 94 | switchMap(name => this.rjax.get(`/user`, {params: name})).subscribe(res => { 95 | this.setState({ 96 | data: res 97 | }); 98 | }, err => console.log('请求出错')); 99 | ); 100 | } 101 | componentWillUnmount() { 102 | // 组件卸载时取消订阅 103 | this.subjectRef.unsubscribe(); 104 | } 105 | search = ({target: value}) => { 106 | this.subject.next({name: value}); 107 | } 108 | render() { 109 | const {data} = this.state; 110 | return ( 111 |
112 | 113 | 116 |
117 | ); 118 | } 119 | } 120 | ``` 121 | 122 | 除了把每个 input 的值都直接转发给 `/user` 请求 componentDidMount() 中的代码还通过下列三个操作符对这些搜索值进行管道处理: 123 | 1. debounceTime(500) - 等待,直到用户停止输入(这个例子中是停止 1/2 秒)。 124 | 2. distinctUntilChanged() - 等待,直到搜索内容发生了变化。 125 | 3. switchMap() - 把搜索请求发送给rjax。 126 | 127 | 这样,只有当用户停止了输入且搜索值和以前不一样的时候,搜索值才会传给rjax发起请求。 128 | 129 | ### switchMap() 130 | 这个 switchMap() 操作符有三个重要的特征: 131 | 1. 它的参数是一个返回 Observable 的函数。rjax.get() 会返回 Observable,其它请求方法也一样。 132 | 2. 如果以前的搜索结果仍然是在途状态(这会出现在慢速网络中),它会取消那个请求,并发起这个新的搜索。 133 | 3. 它会按照原始的请求顺序返回这些服务的响应,而不用关心服务器实际上是以乱序返回的它们。 134 | 135 | ## 配置请求 136 | 待发送请求的option可以通过传给 Rjax 方法最后一个参数中的配置对象进行配置。 137 | 138 | ### 添加请求头 139 | 很多服务器在进行保存型操作时需要额外的请求头。 比如,它们可能需要一个 Content-Type 头来显式定义请求体的 MIME 类型。 也可能服务器会需要一个认证用的令牌(token)。 140 | 141 | ```js 142 | import { HttpHeaders } from 'rjax'; 143 | 144 | const httpOptions = { 145 | headers: new HttpHeaders({ 146 | 'Content-Type': 'application/json', 147 | 'Authorization': 'my-auth-token' 148 | }) 149 | }; 150 | 151 | rjax.post(`/user`,{name: 'xxxx'}, httpOptions) 152 | .subscribe(res => { 153 | console.log(res) 154 | }, err => console.log('请求出错')); 155 | ``` 156 | 157 | ### 修改请求头 158 | 你没法直接修改前述配置对象中的现有头,因为这个 HttpHeaders 类的实例是不可变的。 159 | 160 | 改用 set() 方法代替。 它会返回当前实例的一份克隆,其中应用了这些新修改。 161 | 162 | 比如在发起下一个请求之前,如果旧的令牌已经过期了,你可能还要修改认证头。 163 | 164 | ```js 165 | httpOptions.headers = 166 | httpOptions.headers.set('Authorization', 'my-new-auth-token'); 167 | ``` 168 | 169 | ### URL参数 170 | 171 | 添加 URL 搜索参数也与此类似。 172 | 173 | ```js 174 | import { HttpParams } from 'rjax'; 175 | 176 | searchUsers(name) { 177 | name = name.trim(); 178 | 179 | const options = term ? 180 | { params: new HttpParams().set('name', name) } : {}; 181 | 182 | return this.rjax.get(`/user`, options) 183 | .pipe( 184 | catchError(this.handleError('searchUsers', [])) 185 | ); 186 | } 187 | ``` 188 | 189 | HttpParams 是不可变的,所以你也要使用 set() 方法来修改这些选项。 190 | 191 | ## 错误处理 192 | 193 | 如果这个请求导致了服务器错误怎么办?甚至,在烂网络下请求都没到服务器该怎么办?Rjax 就会返回一个错误(error)而不再是成功的响应。 194 | 195 | 通过在 .subscribe() 中添加第二个回调函数,你可以在组件中处理它: 196 | 197 | ```js 198 | showConfig() { 199 | this.configService.getConfig() 200 | .subscribe( 201 | (data: Config) => this.config = { ...data }, // success path 202 | error => this.error = error // error path 203 | ); 204 | } 205 | ``` 206 | 207 | 在数据访问失败时给用户一些反馈,确实是个好主意。 不过,直接显示由 Rjax 返回的原始错误数据还远远不够。 208 | 209 | ### 获取错误详情 210 | 211 | 检测错误的发生是第一步,不过如果知道具体发生了什么错误才会更有用。上面例子中传给回调函数的 err 参数的类型是 HttpErrorResponse,它包含了这个错误中一些很有用的信息。 212 | 213 | 可能发生的错误分为两种。如果后端返回了一个失败的返回码(如 404、500 等),它会返回一个错误响应体。 214 | 215 | 或者,如果在客户端这边出了错误(比如在 RxJS 操作符 (operator) 中抛出的异常或某些阻碍完成这个请求的网络错误),就会抛出一个 Error 类型的异常。 216 | 217 | Rjax 会在 HttpErrorResponse 中捕获所有类型的错误信息,你可以查看这个响应体以了解到底发生了什么。 218 | 219 | 你可能首先要设计一个错误处理器,就像这样: 220 | 221 | ```js 222 | handleError(error) { 223 | if (error.error instanceof ErrorEvent) { 224 | // A client-side or network error occurred. Handle it accordingly. 225 | console.error('An error occurred:', error.error.message); 226 | } else { 227 | // The backend returned an unsuccessful response code. 228 | // The response body may contain clues as to what went wrong, 229 | console.error( 230 | `Backend returned code ${error.status}, ` + 231 | `body was: ${error.error}`); 232 | } 233 | // return an observable with a user-facing error message 234 | return throwError( 235 | 'Something bad happened; please try again later.'); 236 | }; 237 | ``` 238 | 239 | 注意,该处理器返回一个带有用户友好的错误信息的 RxJS ErrorObservable 对象。 该服务的消费者期望服务的方法返回某种形式的 Observable,就算是“错误的”也可以。 240 | 241 | 现在,你获取了由 Rjax 方法返回的 Observable,并把它们通过管道传给错误处理器。 242 | 243 | ```js 244 | import { catchError } from 'rxjs/operators'; 245 | 246 | getConfig() { 247 | return this.rjax.get(this.configUrl) 248 | .pipe( 249 | catchError(this.handleError) 250 | ); 251 | } 252 | ``` 253 | 254 | ### retry() 255 | 256 | 有时候,错误只是临时性的,只要重试就可能会自动消失。 比如,在移动端场景中可能会遇到网络中断的情况,只要重试一下就能拿到正确的结果。 257 | 258 | RxJS 库提供了几个 retry 操作符,它们值得仔细看看。 其中最简单的是 retry(),它可以对失败的 Observable 自动重新订阅几次。对 HttpClient 方法调用的结果进行重新订阅会导致重新发起 HTTP 请求。 259 | 260 | 把它插入到 Rjax 方法结果的管道中,就放在错误处理器的紧前面。 261 | 262 | ```js 263 | import { retry, catchError } from 'rxjs/operators'; 264 | 265 | getConfig() { 266 | return this.rjax.get(this.configUrl) 267 | .pipe( 268 | retry(3), // retry a failed request up to 3 times 269 | catchError(this.handleError) // then handle the error 270 | ); 271 | } 272 | ``` 273 | 274 | ## 请求非 JSON 格式的数据 275 | 276 | 不是所有的 API 都会返回 JSON 数据。在下面这个例子中的方法会从服务器读取文本文件, 并把文件的内容记录下来,然后把这些内容使用 Observable 的形式返回给调用者。 277 | 278 | ```js 279 | getTextFile(filename) { 280 | // The Observable returned by get() is of type Observable 281 | // because a text response was specified. 282 | // There's no need to pass a type parameter to get(). 283 | return this.rjax.get(filename, {responseType: 'text'}) 284 | .pipe( 285 | tap( // Log the result or error 286 | data => this.log(filename, data), 287 | error => this.logError(filename, error) 288 | ) 289 | ); 290 | } 291 | ``` 292 | 293 | 这里的 rjax.get() 返回字符串而不是默认的 JSON 对象,因为它的 responseType 选项是 'text'。 294 | 295 | 这里的 rjax.get() 返回字符串而不是默认的 JSON 对象,因为它的 responseType 选项是 'text'。 296 | 297 | responseType的可选值有 "arraybuffer" | "blob" | "text" | "json" 298 | 299 | ## 读取完整的响应体 300 | 响应体可能并不包含你需要的全部信息。有时候服务器会返回一个特殊的响应头或状态码,以标记出特定的条件,因此读取它们可能是必要的。 301 | 302 | 要这样做,你就要通过 observe 选项来告诉 Rjax,你想要完整的响应信息,而不是只有响应体: 303 | 304 | ```js 305 | getConfigResponse() { 306 | return this.rjax.get( 307 | this.configUrl, { observe: 'response' }); 308 | } 309 | ``` 310 | 311 | 现在 rjax.get() 会返回一个 [HttpResponse](https://www.angular.cn/api/common/http/HttpResponse) 类型的 Observable,而不只是 JSON 数据。 312 | 313 | ## 监听进度事件 314 | 有时,应用会传输大量数据,并且这些传输可能会花费很长时间。 典型的例子是文件上传。 可以通过在传输过程中提供进度反馈,来提升用户体验。 315 | 316 | 要想开启进度事件的响应,你可以创建一个把 reportProgress 选项设置为 true 的 HttpRequest 实例,以开启进度跟踪事件。 317 | 318 | ```js 319 | import { HttpRequest } from 'rjax'; 320 | 321 | const req = new HttpRequest('POST', '/upload/file', file, { 322 | reportProgress: true 323 | }); 324 | ``` 325 | 326 | ::: warning 327 | 每个进度事件都会触发变更检测,所以,你应该只有当确实希望在 UI 中报告进度时才打开这个选项。 328 | ::: 329 | 330 | 接下来,把这个请求对象传给 rjax.request() 方法,它返回一个 HttpEvents 的 Observable,同样也可以在拦截器中处理这些事件。 331 | 332 | ```js 333 | return this.rjax.request(req).pipe( 334 | map(event => this.getEventMessage(event, file)), 335 | tap(message => this.showProgress(message)), 336 | last(), // return last (completed) message to caller 337 | catchError(this.handleError(file)) 338 | ); 339 | ``` 340 | 341 | getEventMessage 方法会解释事件流中的每一个 HttpEvent 类型。 342 | 343 | ```js 344 | import { HttpEventType } from 'rjax'; 345 | 346 | /** Return distinct message for sent, upload progress, & response events */ 347 | private getEventMessage(event, file) { 348 | switch (event.type) { 349 | case HttpEventType.Sent: 350 | return `Uploading file "${file.name}" of size ${file.size}.`; 351 | 352 | case HttpEventType.UploadProgress: 353 | // Compute and show the % done: 354 | const percentDone = Math.round(100 * event.loaded / event.total); 355 | return `File "${file.name}" is ${percentDone}% uploaded.`; 356 | 357 | case HttpEventType.Response: 358 | return `File "${file.name}" was completely uploaded!`; 359 | 360 | default: 361 | return `File "${file.name}" surprising upload event: ${event.type}.`; 362 | } 363 | } 364 | ``` -------------------------------------------------------------------------------- /docs/guide/example.md: -------------------------------------------------------------------------------- 1 | # 使用示例 2 | 3 | ## 并发请求 4 | 5 | 并发多个请求,等待所有请求都结束后才执行回调相当于 **Promise.all()** 6 | 7 | ```js 8 | import { Rjax } from 'rjax'; 9 | import { combineLatest } from 'rxjs'; 10 | 11 | // 创建实例 12 | const rjax = new Rjax(); 13 | 14 | const p1$ = rjax.get(`/user/12345`); 15 | const p2$ = rjax.get(`/user/123456`); 16 | 17 | combineLatest(p1$, p2$).subscribe(([res1, res2]) => { 18 | // 请求成功回调 19 | console.log(res1); // p1$ 的请求结果 20 | console.log(res2); // p2$ 的请求结果 21 | }, error => { 22 | // 请求失败回调 23 | console.log(error); 24 | }); 25 | ``` 26 | 27 | ## 多数据源合并 28 | 29 | 例如某个图表的数据来源于websocket和ajax手动请求,返回格式是一样的,可以将这两个数据流进行合并创建新的Observable,组件只要订阅合并后的数据流而不关心数据的来源 30 | 31 | ```js 32 | import { Rjax } from 'rjax'; 33 | import { merge } from 'rxjs'; 34 | import { webSocket } from 'rxjs/webSocket'; 35 | 36 | const rjax = new Rjax(); 37 | 38 | const ws$ = webSocket('wss://echo.websocket.org'); // websocket 39 | const ajax$ = rjax.get(`/user/12345`); // ajax 40 | 41 | // 合并数据流 42 | merge(ws$, ajax$).subscribe(res => { 43 | console.log(res) 44 | }, err => console.log('请求出错')); 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | ## 安装 4 | ```bash 5 | yarn add rjax # 或者:npm install rjax --save 6 | ``` 7 | 8 | ## 使用 9 | ```js 10 | import { Rjax } from 'rjax'; 11 | 12 | // 创建实例 13 | const rjax = new Rjax({ 14 | baseURL: 'https://some-domain.com/api/', // 设置请求基路径,可选 15 | timeout: 1000, // 设置请求超时时间,可选 16 | interceptors: [] // 设置请求响应拦截器,可设置多组,可选 17 | xsrfCookieName: 'XSRF-TOKEN', // 是用作 xsrf token 的值的cookie的名称,默认'XSRF-TOKEN',可选 18 | xsrfHeaderName: 'X-XSRF-TOKEN', // 是承载 xsrf token 的值的 HTTP 头的名称,默认'X-XSRF-TOKEN',可选 19 | headers: {}, // 添加统一的headers,默认{},可选 20 | withCredentials: false, // 表示跨域请求时是否需要使用凭证,默认false,可选 21 | jsonp: false, // 是否添加jsonp请求功能,默认false,可选 22 | }); 23 | 24 | // 发起GET请求 25 | rjax.get(`/user/12345`).subscribe(response => { 26 | // 请求成功回调 27 | console.log(response); 28 | }, error => { 29 | // 请求失败回调 30 | console.log(error); 31 | }); 32 | ``` 33 | ### 以下是可用的实例方法 34 | ```ts 35 | class HttpClient { 36 | request(first: string | HttpRequest, url?: string, options: { body?: any; headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | ... 2 more ... | "json"; withCredentials?: boolean; } = {}): Observable 37 | delete(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable 38 | get(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable 39 | head(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable 40 | jsonp(url: string, callbackParam: string): Observable 41 | options(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable 42 | patch(url: string, body: any, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable 43 | post(url: string, body: any, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable 44 | put(url: string, body: any, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: HttpObserve; params?: HttpParams | { [param: string]: string | string[]; }; reportProgress?: boolean; responseType?: "arraybuffer" | "blob" | "text" | "json"; withCredentials?: boolean; } = {}): Observable 45 | } 46 | ``` -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import { Rjax } from './public_api'; 2 | export * from './public_api'; 3 | export default Rjax; 4 | -------------------------------------------------------------------------------- /lib/public_api.ts: -------------------------------------------------------------------------------- 1 | export {Rjax} from './src/rjax'; 2 | export {HttpClient} from './src/client'; 3 | export {HttpHeaders} from './src/headers'; 4 | export {HttpParameterCodec, HttpParams, HttpUrlEncodingCodec} from './src/params'; 5 | export {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpResponseBase, HttpSentEvent, HttpUserEvent} from './src/response'; 6 | export {HttpBackend, HttpHandler} from './src/backend'; 7 | export {HttpRequest} from './src/request'; 8 | export {HttpXhrBackend, XhrFactory} from './src/xhr'; -------------------------------------------------------------------------------- /lib/src/backend.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | import {Observable} from 'rxjs'; 10 | import {HttpRequest} from './request'; 11 | import {HttpEvent} from './response'; 12 | 13 | /** 14 | * Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a 15 | * `HttpResponse`. 16 | * 17 | * `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the 18 | * first interceptor in the chain, which dispatches to the second, etc, eventually reaching the 19 | * `HttpBackend`. 20 | * 21 | * In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain. 22 | * 23 | * @publicApi 24 | */ 25 | export abstract class HttpHandler { 26 | abstract handle(req: HttpRequest): Observable>; 27 | } 28 | 29 | /** 30 | * A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend. 31 | * 32 | * Interceptors sit between the `HttpClient` interface and the `HttpBackend`. 33 | * 34 | * When injected, `HttpBackend` dispatches requests directly to the backend, without going 35 | * through the interceptor chain. 36 | * 37 | * @publicApi 38 | */ 39 | export abstract class HttpBackend implements HttpHandler { 40 | abstract handle(req: HttpRequest): Observable>; 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/cookie.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | export function parseCookieValue(cookieStr: string, name: string): string|null { 10 | name = encodeURIComponent(name); 11 | for (const cookie of cookieStr.split(';')) { 12 | const eqIndex = cookie.indexOf('='); 13 | const [cookieName, cookieValue]: string[] = 14 | eqIndex == -1 ? [cookie, ''] : [cookie.slice(0, eqIndex), cookie.slice(eqIndex + 1)]; 15 | if (cookieName.trim() === name) { 16 | return decodeURIComponent(cookieValue); 17 | } 18 | } 19 | return null; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/handler.ts: -------------------------------------------------------------------------------- 1 | import { HttpHandler, HttpBackend } from './backend'; 2 | import { Observable } from 'rxjs'; 3 | import { HttpRequest } from './request'; 4 | import { HttpInterceptorHandler, HttpInterceptor, NoopInterceptor } from './interceptor'; 5 | import { HttpEvent } from './response'; 6 | import { HttpXhrBackend } from './xhr'; 7 | 8 | export class HttpInterceptingHandler implements HttpHandler { 9 | private chain: HttpHandler|null = null; 10 | private backend: HttpBackend = new HttpXhrBackend(); 11 | constructor(private interceptors: HttpInterceptor[] = [new NoopInterceptor()]) {} 12 | 13 | handle(req: HttpRequest): Observable> { 14 | if (this.chain === null) { 15 | this.chain = this.interceptors.reduceRight( 16 | (next, interceptor) => new HttpInterceptorHandler(next, interceptor), this.backend); 17 | } 18 | return this.chain.handle(req); 19 | } 20 | } 21 | 22 | /** 23 | * Constructs an `HttpHandler` that applies interceptors 24 | * to a request before passing it to the given `HttpBackend`. 25 | * 26 | * Use as a factory function within `HttpClientModule`. 27 | * 28 | * 29 | */ 30 | export function interceptingHandler( 31 | backend: HttpBackend, interceptors: HttpInterceptor[] | null = []): HttpHandler { 32 | if (!interceptors) { 33 | return backend; 34 | } 35 | return interceptors.reduceRight( 36 | (next, interceptor) => new HttpInterceptorHandler(next, interceptor), backend); 37 | } 38 | 39 | /** 40 | * Factory function that determines where to store JSONP callbacks. 41 | * 42 | * Ordinarily JSONP callbacks are stored on the `window` object, but this may not exist 43 | * in test environments. In that case, callbacks are stored on an anonymous object instead. 44 | * 45 | * 46 | */ 47 | export function jsonpCallbackContext(): Object { 48 | if (typeof window === 'object') { 49 | return window; 50 | } 51 | return {}; 52 | } -------------------------------------------------------------------------------- /lib/src/headers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | interface Update { 10 | name: string; 11 | value?: string|string[]; 12 | op: 'a'|'s'|'d'; 13 | } 14 | 15 | /** 16 | * Immutable set of Http headers, with lazy parsing. 17 | * 18 | * @publicApi 19 | */ 20 | export class HttpHeaders { 21 | /** 22 | * Internal map of lowercase header names to values. 23 | */ 24 | // TODO(issue/24571): remove '!'. 25 | private headers !: Map; 26 | 27 | 28 | /** 29 | * Internal map of lowercased header names to the normalized 30 | * form of the name (the form seen first). 31 | */ 32 | private normalizedNames: Map = new Map(); 33 | 34 | /** 35 | * Complete the lazy initialization of this object (needed before reading). 36 | */ 37 | // TODO(issue/24571): remove '!'. 38 | private lazyInit !: HttpHeaders | Function | null; 39 | 40 | /** 41 | * Queued updates to be materialized the next initialization. 42 | */ 43 | private lazyUpdate: Update[]|null = null; 44 | 45 | constructor(headers?: string|{[name: string]: string | string[]}) { 46 | if (!headers) { 47 | this.headers = new Map(); 48 | } else if (typeof headers === 'string') { 49 | this.lazyInit = () => { 50 | this.headers = new Map(); 51 | headers.split('\n').forEach(line => { 52 | const index = line.indexOf(':'); 53 | if (index > 0) { 54 | const name = line.slice(0, index); 55 | const key = name.toLowerCase(); 56 | const value = line.slice(index + 1).trim(); 57 | this.maybeSetNormalizedName(name, key); 58 | if (this.headers.has(key)) { 59 | this.headers.get(key) !.push(value); 60 | } else { 61 | this.headers.set(key, [value]); 62 | } 63 | } 64 | }); 65 | }; 66 | } else { 67 | this.lazyInit = () => { 68 | this.headers = new Map(); 69 | Object.keys(headers).forEach(name => { 70 | let values: string|string[] = headers[name]; 71 | const key = name.toLowerCase(); 72 | if (typeof values === 'string') { 73 | values = [values]; 74 | } 75 | if (values.length > 0) { 76 | this.headers.set(key, values); 77 | this.maybeSetNormalizedName(name, key); 78 | } 79 | }); 80 | }; 81 | } 82 | } 83 | 84 | /** 85 | * Checks for existence of header by given name. 86 | */ 87 | has(name: string): boolean { 88 | this.init(); 89 | 90 | return this.headers.has(name.toLowerCase()); 91 | } 92 | 93 | /** 94 | * Returns first header that matches given name. 95 | */ 96 | get(name: string): string|null { 97 | this.init(); 98 | 99 | const values = this.headers.get(name.toLowerCase()); 100 | return values && values.length > 0 ? values[0] : null; 101 | } 102 | 103 | /** 104 | * Returns the names of the headers 105 | */ 106 | keys(): string[] { 107 | this.init(); 108 | 109 | return Array.from(this.normalizedNames.values()); 110 | } 111 | 112 | /** 113 | * Returns list of header values for a given name. 114 | */ 115 | getAll(name: string): string[]|null { 116 | this.init(); 117 | 118 | return this.headers.get(name.toLowerCase()) || null; 119 | } 120 | 121 | append(name: string, value: string|string[]): HttpHeaders { 122 | return this.clone({name, value, op: 'a'}); 123 | } 124 | 125 | set(name: string, value: string|string[]): HttpHeaders { 126 | return this.clone({name, value, op: 's'}); 127 | } 128 | 129 | delete (name: string, value?: string|string[]): HttpHeaders { 130 | return this.clone({name, value, op: 'd'}); 131 | } 132 | 133 | private maybeSetNormalizedName(name: string, lcName: string): void { 134 | if (!this.normalizedNames.has(lcName)) { 135 | this.normalizedNames.set(lcName, name); 136 | } 137 | } 138 | 139 | private init(): void { 140 | if (!!this.lazyInit) { 141 | if (this.lazyInit instanceof HttpHeaders) { 142 | this.copyFrom(this.lazyInit); 143 | } else { 144 | this.lazyInit(); 145 | } 146 | this.lazyInit = null; 147 | if (!!this.lazyUpdate) { 148 | this.lazyUpdate.forEach(update => this.applyUpdate(update)); 149 | this.lazyUpdate = null; 150 | } 151 | } 152 | } 153 | 154 | private copyFrom(other: HttpHeaders) { 155 | other.init(); 156 | Array.from(other.headers.keys()).forEach(key => { 157 | this.headers.set(key, other.headers.get(key) !); 158 | this.normalizedNames.set(key, other.normalizedNames.get(key) !); 159 | }); 160 | } 161 | 162 | private clone(update: Update): HttpHeaders { 163 | const clone = new HttpHeaders(); 164 | clone.lazyInit = 165 | (!!this.lazyInit && this.lazyInit instanceof HttpHeaders) ? this.lazyInit : this; 166 | clone.lazyUpdate = (this.lazyUpdate || []).concat([update]); 167 | return clone; 168 | } 169 | 170 | private applyUpdate(update: Update): void { 171 | const key = update.name.toLowerCase(); 172 | switch (update.op) { 173 | case 'a': 174 | case 's': 175 | let value = update.value !; 176 | if (typeof value === 'string') { 177 | value = [value]; 178 | } 179 | if (value.length === 0) { 180 | return; 181 | } 182 | this.maybeSetNormalizedName(update.name, key); 183 | const base = (update.op === 'a' ? this.headers.get(key) : undefined) || []; 184 | base.push(...value); 185 | this.headers.set(key, base); 186 | break; 187 | case 'd': 188 | const toDelete = update.value as string | undefined; 189 | if (!toDelete) { 190 | this.headers.delete(key); 191 | this.normalizedNames.delete(key); 192 | } else { 193 | let existing = this.headers.get(key); 194 | if (!existing) { 195 | return; 196 | } 197 | existing = existing.filter(value => toDelete.indexOf(value) === -1); 198 | if (existing.length === 0) { 199 | this.headers.delete(key); 200 | this.normalizedNames.delete(key); 201 | } else { 202 | this.headers.set(key, existing); 203 | } 204 | } 205 | break; 206 | } 207 | } 208 | 209 | /** 210 | * @internal 211 | */ 212 | forEach(fn: (name: string, values: string[]) => void) { 213 | this.init(); 214 | Array.from(this.normalizedNames.keys()) 215 | .forEach(key => fn(this.normalizedNames.get(key) !, this.headers.get(key) !)); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /lib/src/interceptor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | import {Observable} from 'rxjs'; 10 | 11 | import {HttpHandler} from './backend'; 12 | import {HttpRequest} from './request'; 13 | import {HttpEvent} from './response'; 14 | import { HttpHeaders } from './headers'; 15 | import { timeout } from 'rxjs/operators'; 16 | 17 | export interface DefaultConfig { 18 | baseURL: string; 19 | timeout: number; 20 | xsrfCookieName: string; 21 | xsrfHeaderName: string; 22 | withCredentials: boolean; 23 | headers: {[header: string]: string | string[]}; 24 | jsonp: boolean; 25 | interceptors?: HttpInterceptor[]; 26 | } 27 | 28 | /** 29 | * Creates a new URL by combining the specified URLs 30 | * 31 | * @param {string} baseURL The base URL 32 | * @param {string} relativeURL The relative URL 33 | * @returns {string} The combined URL 34 | */ 35 | function combineURLs(baseURL: string, relativeURL: string) { 36 | return relativeURL 37 | ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') 38 | : baseURL; 39 | } 40 | 41 | /** 42 | * Determines whether the specified URL is absolute 43 | * 44 | * @param {string} url The URL to test 45 | * @returns {boolean} True if the specified URL is absolute, otherwise false 46 | */ 47 | function isAbsoluteURL(url: string) { 48 | // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). 49 | // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed 50 | // by any combination of letters, digits, plus, period, or hyphen. 51 | return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); 52 | } 53 | 54 | /** 55 | * Intercepts `HttpRequest` and handles them. 56 | * 57 | * Most interceptors will transform the outgoing request before passing it to the 58 | * next interceptor in the chain, by calling `next.handle(transformedReq)`. 59 | * 60 | * In rare cases, interceptors may wish to completely handle a request themselves, 61 | * and not delegate to the remainder of the chain. This behavior is allowed. 62 | * 63 | * @publicApi 64 | */ 65 | export interface HttpInterceptor { 66 | /** 67 | * Intercept an outgoing `HttpRequest` and optionally transform it or the 68 | * response. 69 | * 70 | * Typically an interceptor will transform the outgoing request before returning 71 | * `next.handle(transformedReq)`. An interceptor may choose to transform the 72 | * response event stream as well, by applying additional Rx operators on the stream 73 | * returned by `next.handle()`. 74 | * 75 | * More rarely, an interceptor may choose to completely handle the request itself, 76 | * and compose a new event stream instead of invoking `next.handle()`. This is 77 | * acceptable behavior, but keep in mind further interceptors will be skipped entirely. 78 | * 79 | * It is also rare but valid for an interceptor to return multiple responses on the 80 | * event stream for a single request. 81 | */ 82 | intercept(req: HttpRequest, next: HttpHandler): Observable>; 83 | } 84 | 85 | /** 86 | * `HttpHandler` which applies an `HttpInterceptor` to an `HttpRequest`. 87 | * 88 | * 89 | */ 90 | export class HttpInterceptorHandler implements HttpHandler { 91 | constructor(private next: HttpHandler, private interceptor: HttpInterceptor) {} 92 | 93 | handle(req: HttpRequest): Observable> { 94 | return this.interceptor.intercept(req, this.next); 95 | } 96 | } 97 | 98 | /** 99 | * A multi-provider token which represents the array of `HttpInterceptor`s that 100 | * are registered. 101 | * 102 | * @publicApi 103 | */ 104 | 105 | export class NoopInterceptor implements HttpInterceptor { 106 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 107 | return next.handle(req); 108 | } 109 | } 110 | 111 | export class DefaultInterceptor implements HttpInterceptor { 112 | constructor(private config: DefaultConfig) {} 113 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 114 | let headers!: HttpHeaders; 115 | if (typeof this.config.headers === 'object') { 116 | for (const key in this.config.headers) { 117 | if (this.config.headers.hasOwnProperty(key)) { 118 | headers = req.headers.set(key, this.config.headers[key]); 119 | } 120 | } 121 | } 122 | let url = req.url; 123 | // Support baseURL config 124 | if (this.config.baseURL && !isAbsoluteURL(url)) { 125 | url = combineURLs(this.config.baseURL, url); 126 | } 127 | const defaultReq = req.clone({ 128 | url, 129 | headers, 130 | withCredentials: this.config.withCredentials 131 | }); 132 | if (Number(this.config.timeout) > 0) { 133 | return next.handle(defaultReq).pipe(timeout(this.config.timeout)); 134 | } 135 | return next.handle(defaultReq); 136 | } 137 | } -------------------------------------------------------------------------------- /lib/src/jsonp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | import {Observable, Observer} from 'rxjs'; 10 | 11 | import {HttpBackend, HttpHandler} from './backend'; 12 | import {HttpRequest} from './request'; 13 | import {HttpErrorResponse, HttpEvent, HttpEventType, HttpResponse} from './response'; 14 | import { jsonpCallbackContext } from './handler'; 15 | 16 | // Every request made through JSONP needs a callback name that's unique across the 17 | // whole page. Each request is assigned an id and the callback name is constructed 18 | // from that. The next id to be assigned is tracked in a global variable here that 19 | // is shared among all applications on the page. 20 | let nextRequestId: number = 0; 21 | 22 | // Error text given when a JSONP script is injected, but doesn't invoke the callback 23 | // passed in its URL. 24 | export const JSONP_ERR_NO_CALLBACK = 'JSONP injected script did not invoke callback.'; 25 | 26 | // Error text given when a request is passed to the JsonpClientBackend that doesn't 27 | // have a request method JSONP. 28 | export const JSONP_ERR_WRONG_METHOD = 'JSONP requests must use JSONP request method.'; 29 | export const JSONP_ERR_WRONG_RESPONSE_TYPE = 'JSONP requests must use Json response type.'; 30 | 31 | /** 32 | * DI token/abstract type representing a map of JSONP callbacks. 33 | * 34 | * In the browser, this should always be the `window` object. 35 | * 36 | * 37 | */ 38 | export abstract class JsonpCallbackContext { [key: string]: (data: any) => void; } 39 | 40 | /** 41 | * `HttpBackend` that only processes `HttpRequest` with the JSONP method, 42 | * by performing JSONP style requests. 43 | * 44 | * @publicApi 45 | */ 46 | export class JsonpClientBackend implements HttpBackend { 47 | private document = document; 48 | private callbackMap: JsonpCallbackContext = jsonpCallbackContext(); 49 | constructor() {} 50 | 51 | /** 52 | * Get the name of the next callback method, by incrementing the global `nextRequestId`. 53 | */ 54 | private nextCallback(): string { return `ng_jsonp_callback_${nextRequestId++}`; } 55 | 56 | /** 57 | * Process a JSONP request and return an event stream of the results. 58 | */ 59 | handle(req: HttpRequest): Observable> { 60 | // Firstly, check both the method and response type. If either doesn't match 61 | // then the request was improperly routed here and cannot be handled. 62 | if (req.method !== 'JSONP') { 63 | throw new Error(JSONP_ERR_WRONG_METHOD); 64 | } else if (req.responseType !== 'json') { 65 | throw new Error(JSONP_ERR_WRONG_RESPONSE_TYPE); 66 | } 67 | 68 | // Everything else happens inside the Observable boundary. 69 | return new Observable>((observer: Observer>) => { 70 | // The first step to make a request is to generate the callback name, and replace the 71 | // callback placeholder in the URL with the name. Care has to be taken here to ensure 72 | // a trailing &, if matched, gets inserted back into the URL in the correct place. 73 | const callback = this.nextCallback(); 74 | const url = req.urlWithParams.replace(/=JSONP_CALLBACK(&|$)/, `=${callback}$1`); 75 | 76 | // Construct the