rxjs 发送http请求
当我刚开始学习RxJS时,我本能地看到,可观察到的流为解决我在前端Web应用程序开发中每天遇到的许多问题提供了各种可能性。 我已经使用了一段时间的flux体系结构,并为组织结构的清晰性和它给我的Web应用程序带来的关注点分离而震惊。 我读过RxJS可以做同样的事情,并且热衷于学习如何做。 优雅地处理HTTP请求似乎是此学习过程的明显起点。
但是,我很快就感到沮丧,因为我在一个地方只能找到很少的关于该主题的良好实践的信息(尤其是错误处理),并且不得不四处阅读大部分零碎的东西,尽管阅读,大量浏览Stackoverflow和Github问题线程以及个人实验。 本文是到目前为止我所学的目录。
我将解释一些方便的方法来进行以下操作:
-从HTTP请求创建可观察的流
-处理HTTP错误响应
-乱序HTTP请求完成的优雅处理
-限制用户输入
-一些额外的技巧提示
我将假设一些有关创建和订阅可观察对象的绝对基础知识,因为这很容易在网上找到 ,这也是我开始尝试RxJS和HTTP请求时所处的位置。
示例应用
为了演示所有这些技术,我们将创建一个使用g ithub用户api的示例微型应用程序。 它将使用户能够在框中键入github用户名,如果有效,则在下面显示其头像。 我将使用该应用程序的许多变体来演示使用RxJS的不同方法。 为了简单起见,该应用程序仅使用RxJS,引导程序和少量jQuery将页面粘合在一起。
注意:github用户api,如果未经身份验证,则每小时60个请求的速率相当可悲。 因此,如果您对示例感到满意,它们可能会停止工作一会儿。
设置
首先,让我们创建一个文本输入,并通过其“ keyup”事件创建一个可观察的对象。
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<button
id="search-button"
class="btn btn-primary"
>
Search
</button>
</div>
let userClicksSearchButton = Rx.Observable.fromEvent(
$( "#search-button" ),
"click"
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userClicksSearchButton.subscribe( ( searchTerm ) => {
alert(searchTerm);
});
示例1:输入框
在框中键入内容,然后单击搜索按钮以查看包含文本输入值的警报。 请注意fromEvent
可观察对象之后链接的额外map
。 这使我们能够将搜索输入框的当前输入值映射到我们可观察到的userClicksSearchButton
,从而替换通常会发出的默认事件对象。
处理HTTP请求
好的,我们现在有了一个可以观察的东西,当用户按下搜索按钮时,它将发出用户的输入。 为了触发我们的HTTP请求,我们将创建一个可观察的流,我们可以使用userClicksSearchButton作为源来订阅:
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<button
id="search-button"
class="btn btn-primary"
>
Search
</button>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
</div>
let userClicksSearchButton = Rx.Observable.fromEvent(
$( "#search-button" ),
'click'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userClicksSearchButton
.flatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
);
})
.subscribe( ( response ) => {
renderUser(
response.login,
response.html_url,
response.avatar_url
);
});
function renderUser ( login, href, imgSrc ) {
let searchResult = $( "#search-result" );
searchResult.show();
searchResult.attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
示例2:按钮搜索
尝试在框中输入您的Github用户名并点击搜索,您应该会看到您的头像显示在下方。 请注意,当前,如果您搜索无效的用户名,它将破坏应用程序。 现在不用担心这个。 我们将对其进行一些整理。
我们在第一个可观察对象之后链接了flatMap
,订阅了所得的可观察对象,并将一些接收到的数据写入了DOM。
我们已经进行了使用标准的jQuery GET,这是我们裹在RxJS”乐于助人的要求fromPromise
把它变成可观察到的。
平面图
看一下示例中`flatMap`的用法。 我必须承认,当我第一次看这类事情的几个例子时,这让我很困惑。 乍一看,似乎我们应该能够像单击事件一样调用map
,毕竟,我们已经通过调用fromPromise
将诺言转换为可观察的fromPromise
吗?
其实不然,什么fromPromise
回报为承诺的可观察到的数据流,而不是物体流,当它解决了承诺会发光。 flatMap
允许我们将所有这些promise解析度扁平化为单个可观察的流,并且当我们订阅它时,我们仅获得jQuery最初在then()
上发出的响应对象。
处理错误
之前我曾提到,如果您搜索无效的用户名,则该应用程序当前会中断(尝试搜索一些乱码,然后再输入完全正确的内容)。 显然,这很糟糕,而最初,为什么发生这种情况一点也不明显。 为了理解原因,让我们看一下在诺言解决时发生的事件的顺序:
1. HTTP请求完成,jQuery拒绝了Promise(因为它是404)
2. fromPromise
创建的可观察fromPromise
抛出错误,因为这是它对被拒绝的承诺的React
3.错误未被捕获,因此在可观察的父级flatMap
再次引发
4. flatMap
observable将不再发出,因为在observable流抛出错误之后,它将终止。
这具有意想不到的副作用,即每次收到错误响应时,我们的搜索按钮都将失效。 这不是很好,那么我们如何解决这个问题呢?
这再次是应用程序,但是这次有错误处理:
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<button
id="search-button"
class="btn btn-primary"
>
Search
</button>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
<!-- Error message -->
<div
id="error"
class="well"
style="display:none;"
>
<p id="error__message">
</p>
</div>
</div>
let userClicksSearchButton = Rx.Observable.fromEvent(
$( "#search-button" ),
'click'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userClicksSearchButton
.flatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( ( response ) => {
renderError(response.statusText);
return Rx.Observable.empty();
});
})
.subscribe( ( response ) => {
renderUser(
response.login,
response.html_url,
response.avatar_url
);
});
function renderUser ( login, href, imgSrc ) {
$( "#search-result" ).show();
$( "#error" ).hide();
$( "#search-result" ).attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
function renderError ( message ) {
$( "#search-result" ).hide();
$( "#error" ).show();
$( "#error__message" ).text(message);
}
示例3:具有错误处理的按钮搜索
现在,我们在错误向上游传播之前捕获并处理该错误,并用一个空的已完成的可观察对象替换该可观察对象,该错误将在该位置变平。 为此,我们使用方便的Rx.Observable.empty()
。
处理乱序请求
让我们稍微修改一下示例。 假设我们希望用户不必在按钮上进行搜索,而是希望能够在框中键入内容,并在键入时进行搜索。 为此,我们需要将userClicksSearchButton observable替换为可观察的userTypesInSearchBox,如下所示:
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
</div>
let userTypesInSearchBox = Rx.Observable.fromEvent(
$( "#search-box" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userTypesInSearchBox
.flatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
).catch( () => Rx.Observable.empty());
})
.subscribe( ( response ) => {
renderUser(
response.login,
response.html_url,
response.avatar_url
);
});
function renderUser ( login, href, imgSrc ) {
let searchResult = $( "#search-result" );
searchResult.show();
searchResult.attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
示例4:文本搜索
现在我们发现,由于我们的每个请求都是快速连续触发的,因此我们不能再保证它们将按启动顺序完成。 这可能导致我们的搜索结果导致下方显示不匹配的头像。 这不是很好,因此要解决此问题,我们将使用concatMap。
Concat地图
concatMap
与flatMap
非常相似,除了它会保留源发射的顺序,即使它正在变平的可观察对象以不同的顺序发射也是如此。 例如,如果我搜索“ Elle”,然后立即搜索“ Ellen”,并且恰好首先完成了“ Ellen”请求,则concatMap
将等到“ Elle”请求完成后,才发出立即连续的两个结果。订单'Elle''Ellen'。
这是修改为使用“ concatMap”的代码。
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
<!-- Error message -->
<div
id="error"
class="well"
style="display:none;"
>
<p id="error__message">
</p>
</div>
</div>
let userTypesInSearchBox = Rx.Observable.fromEvent(
$( "#search-box" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userTypesInSearchBox
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( () => Rx.Observable.empty());
})
.subscribe( ( response ) => {
renderUser(
response.login,
response.html_url,
response.avatar_url
);
});
function renderUser ( login, href, imgSrc ) {
$( "#search-result" ).show();
$( "#search-result" ).attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
示例5:文本搜索保留请求顺序
编辑:我刚刚了解到我们也可以switchMap
使用switchMap
。 如果需要的话,这提供了取消基础冗余http请求的附加优势。
限制用户输入
目前,我们的应用程序每次用户在框中输入字母时都会触发一个请求。 考虑到用户在框中至少输入几个字母之前对查看结果并不真正感兴趣,因此这似乎有点过头了。 当服务器没有增加用户体验时,为什么还要锤打服务器? 使用RxJS,我们可以通过对“ debounce”进行简单的额外函数调用来缓解此问题。
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
</div>
let userTypesInSearchBox = Rx.Observable.fromEvent(
$( "#search-box" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userTypesInSearchBox
.debounce( 250 )
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( () => Rx.Observable.empty());
})
.subscribe( ( result ) => {
renderUser(
result.login,
result.html_url,
result.avatar_url,
result.searchTerm
);
});
function renderUser ( login, href, imgSrc ) {
$( "#search-result" ).show();
$( "#search-result" ).attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
示例6:限制文本搜索
在此示例中,当您在框中键入内容时,值得一看的是在控制台窗口的“网络”选项卡中,以查看发送的请求比上一个示例少得多。
去抖
“反跳”接受一个数字参数,该参数表示在上一次发射之后可观察到的信号在再次发射之前应等待的毫秒数。 时间过去后,可观察对象将仅发射该时间段内的最后一个发射,而忽略任何其他发射。 我发现这对于基于文本的实时搜索功能非常有用。
注意:如果您需要它发出相反的声音,即该时间段内的第一个发射,则可以改用throttle
方法。
额外的提示和技巧
上面的技术将处理大多数HTTP请求用例,但是您可以做一些其他很酷的事情来减轻异步请求处理带来的痛苦:
采取
有时,您只对前1个,2个或n个用户交互感兴趣。 对于这些情况,RxJS提供了“ take”方法。 在我们的搜索按钮示例中,假设我们只想显示第一个有效结果:
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
</div>
let userTypesInSearchBox = Rx.Observable.fromEvent(
$( "#search-box" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userTypesInSearchBox
.debounce( 250 )
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( () => Rx.Observable.empty());
})
.take( 1 )
.subscribe( ( response ) => {
renderUser(
response.login,
response.html_url,
response.avatar_url
);
});
function renderUser ( login, href, imgSrc ) {
$( "#search-result" ).show();
$( "#search-result" ).attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
示例7:使用Take进行文本搜索
现在,执行的第一个搜索将起作用,但是随后在框中键入的所有内容都将被忽略。
过滤
有时我们想根据条件过滤掉某些可观察到的排放物。 为了演示,让我们使用filter
器使我们的文本搜索示例忽略搜索,直到它们的长度至少为5个字符:
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
</div>
let userTypesInSearchBox = Rx.Observable.fromEvent(
$( "#search-box" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userTypesInSearchBox
.debounce( 250 )
.filter( ( searchTerm ) => {
return searchTerm.length >= 5 ;
})
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( ( response ) => Rx.Observable.empty());
})
.subscribe( ( response ) => {
renderUser(
response.login,
response.html_url,
response.avatar_url
);
});
function renderUser ( login, href, imgSrc ) {
$( "#search-result" ).show();
$( "#search-result" ).attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
示例8:筛选出的文本搜索
合并
有时,将多个可观察物合并为一个可观察物很有用。 为了演示,让我们使用merge
为我们的搜索应用程序提供两个输入,您可以通过键入以下任意一个来进行搜索:
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box-1">
Username:
</label>
<input
id="search-box-1"
type="text"
class="form-control"
/>
</div>
<div>
<label for="search-box-2">
Username:
</label>
<input
id="search-box-2"
type="text"
class="form-control"
/>
</div>
<hr />
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
</div>
let userTypesInSearchBox1 = Rx.Observable.fromEvent(
$( "#search-box-1" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box-1" ).val();
});
let userTypesInSearchBox2 = Rx.Observable.fromEvent(
$( "#search-box-2" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box-2" ).val();
});
userTypesInSearchBox1
.merge(userTypesInSearchBox2)
.debounce( 250 )
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( () => Rx.Observable.empty());
})
.subscribe( ( response ) => {
renderUser(
response.login,
response.html_url,
response.avatar_url
);
});
function renderUser ( login, href, imgSrc ) {
$( "#search-result" ).show();
$( "#search-result" ).attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
}
示例9:合并文本搜索
保留数据以备后用
上面所有示例的潜在问题是,当我们处于可观察流的后期时,在请求完成之后,我们将丢失对原始有效负载的引用(在这种情况下,我们的搜索词文本)。 我们可以避免这种情况,将原始有效负载与响应有效负载捆绑在一起,而我们仍然可以通过词法作用域来访问它。 例如,假设我们想将搜索词再次作为render
功能的一部分写入页面:
<divclass = "container" >
<!-- Search controls -->
<h1>Search Github Users</h1>
<div class="form-group">
<label for="search-box">
Username:
</label>
<input
id="search-box"
type="text"
class="form-control"
/>
</div>
<hr />
<!-- Search Term -->
<div>
<span>
<strong>Search Term:</strong>
</span>
<span id="search-term-text"></span>
</div>
<!-- Search result -->
<a
href=""
target="_blank"
id="search-result"
style="display:none;"
>
<h2 id="search-result__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result__avatar"
/>
</a>
</div>
let userTypesInSearchBox = Rx.Observable.fromEvent(
$( "#search-box" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box" ).val();
});
userTypesInSearchBox
.debounce( 250 )
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.map( ( response ) => {
return {
response : response,
searchTerm : searchTerm
}
})
.catch( () => Rx.Observable.empty());
})
.subscribe( ( result ) => {
renderUser(
result.response.login,
result.response.html_url,
result.response.avatar_url,
result.searchTerm
);
});
function renderUser ( login, href, imgSrc, searchTerm ) {
$( "#search-result" ).show();
$( "#search-result" ).attr( "href" , href);
$( "#search-result__avatar" ).attr( 'src' , imgSrc);
$( '#search-result__login' ).text(login);
$( '#search-term-text' ).text(searchTerm);
}
示例10:带有保留数据的文本搜索
结合最新
RxJS的最佳功能之一是将多个流合并为一个流是多么容易。 为了演示,让我们修改示例以进行两次搜索,并允许用户比较所产生的化身彼此相邻,但只有在两次搜索都得到结果之后:
<divclass = "container" >
<!-- Search controls -->
<h1>Compare Github Users</h1>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label for="search-box">
Username 1:
</label>
<input
id="search-box-1"
type="text"
class="form-control"
/>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label for="search-box">
Username 2:
</label>
<input
id="search-box-2"
type="text"
class="form-control"
/>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-xs-6">
<!-- Search result 1 -->
<a
href=""
target="_blank"
id="search-result-1"
style="display:none;"
>
<h2 id="search-result-1__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result-1__avatar"
/>
</a>
</div>
<div class="col-xs-6">
<!-- Search result 2 -->
<a
href=""
target="_blank"
id="search-result-2"
style="display:none;"
>
<h2 id="search-result-2__login"></h2>
<img
src=""
alt="avatar"
width="150px"
height="150px"
id="search-result-2__avatar"
/>
</a>
</div>
</div>
</div>
let userTypesInSearchBox1 = Rx.Observable.fromEvent(
$( "#search-box-1" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box-1" ).val();
});
let userTypesInSearchBox2 = Rx.Observable.fromEvent(
$( "#search-box-2" ),
'keyup'
)
.map( ( event ) => {
return $( "#search-box-2" ).val();
});
let searchResult1 = userTypesInSearchBox1
.debounce( 250 )
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( ( response ) => {
return Rx.Observable.empty();
});
});
let searchResult2 = userTypesInSearchBox2
.debounce( 250 )
.concatMap( ( searchTerm ) => {
return Rx.Observable.fromPromise(
$.get( 'https://api.github.com/users/' + searchTerm)
)
.catch( ( response ) => {
return Rx.Observable.empty();
});
});
Rx.Observable
.combineLatest(searchResult1, searchResult2)
.subscribe( ( results ) => {
renderUsers(
results[ 0 ].login,
results[ 0 ].html_url,
results[ 0 ].avatar_url,
results[ 1 ].login,
results[ 1 ].html_url,
results[ 1 ].avatar_url
);
});
function renderUsers (
login1,
href1,
imgSrc1,
login2,
href2,
imgSrc2
) {
$( "#search-result-1" ).show();
$( "#search-result-1" ).attr( "href" , href1);
$( "#search-result-1__avatar" ).attr( 'src' , imgSrc1);
$( '#search-result-1__login' ).text(login1);
$( "#search-result-2" ).show();
$( "#search-result-2" ).attr( "href" , href2);
$( "#search-result-2__avatar" ).attr( 'src' , imgSrc2);
$( '#search-result-2__login' ).text(login2);
}
示例11:结合最新的文本搜索
接下来是什么?
有很多方法可以充分利用RxJS的强大功能来处理HTTP请求,我们已经介绍了其中的一些方法。 但是,RxJS API是一个复杂的野兽,我们只是从表面上介绍了RxJS方法可以使用HTTP流完成各种操作的各种方式。 一旦掌握了一些知识,就值得检查一下文档以尝试更多的文档 ,这些文档可能会对更具体的用例有所帮助
翻译自: https://hackernoon.com/using-rxjs-to-handle-http-requests-what-ive-learned-4640aaf4646c
rxjs 发送http请求