js异步请求
目前async / await
特性并没有被添加到ES2016标准中,但不代表这些特性将来不会被加入到Javascript中。在我写这篇文章时,它已经到达第三版草案,并且正迅速的发展中。这些特性已经被IE Edge支持了,而且它将会到达第四版,届时该特性将会登陆其他浏览器 -- 为加入该语言的下一版本而铺路(也可以看看:TC39进程)。
我们听说特性已经有一段时间了,现在让我们深入它,并了解它是如何工作的。为了能够了解这篇文章的内容,你需要对promise和生成器对象有深厚的理解。这些资源或许可以帮到你。
使用Promise
让我们假设我们有像下面这样的代码。在这里我将一个HTTP请求包装在一个Promise
对象中。这个Promise在成功时会返回body
对象,被拒绝时会将原因err
返回。它每次都会在本博客(原作者博客)中为一篇随机文章拉取html内容。
var request = require('request');
function getRandomPonyFooArticle () {
return new Promise((resolve, reject) => {
request('https://ponyfoo.com/articles/random', (err, res, body) => {
if (err) {
reject(err); return;
}
resolve(body);
});
});
}
上述的promise代码的典型用法是像下面写的这样。 在那里,我们新建了一个promise链来将HTML页面中的DOM对象的一个子集转换成Markdown,然后再转换成对终端友好的输出, 最终再使用console.log
输出它。 永远要记得为你的promise添加.catch
处理器。
var hget = require('hget');
var marked = require('marked');
var Term = require('marked-terminal');
printRandomArticle();
function printRandomArticle () {
getRandomPonyFooArticle()
.then(html => hget(html, {
markdown: true,
root: 'main',
ignore: '.at-subscribe,.mm-comments,.de-sidebar'
}))
.then(md => marked(md, {
renderer: new Term()
}))
.then(txt => console.log(txt))
.catch(reason => console.error(reason));
}
当代码运行后,这段代码将产生像以下截图所示的输出。
上面那段代码就是“比用回调函数更好”的写法,它能让你感觉像在按顺序的阅读代码。
使用生成器(generator)
过去,通过探索,我们发现生成器可以用一种“同步”合成的方法来获得html
。即使现在的代码有一些同步写法,其中还是涉及相当多的包装,而且生成器可能不是最直截了当的达到我们期望结果的方法,最终可能无论如何我们都会坚持改为使用promise。
function getRandomPonyFooArticle (gen) {
var g = gen();
request('https://ponyfoo.com/articles/random', (err, res, body) => {
if (err) {
g.throw(err); return;
}
g.next(body);
});
}
getRandomPonyFooArticle(function* printRandomArticle () {
var html = yield;
var md = hget(html, {
markdown: true,
root: 'main',
ignore: '.at-subscribe,.mm-comments,.de-sidebar'
});
var txt = marked(md, {
renderer: new Term()
});
console.log(txt);
});
“请记住,在使用promise时,你应该将yield调用包装在try/catch块中来保留我们添加的错误处理器”
不说你也知道,像这样使用生成器并不容易扩展。除了涉及直观的语法的混入,你的迭代代码会高度耦合到生成器函数中,这将会降低扩展性。这表示你在添加新的await表达式到生成器中时需要经常修改它。一个更好的替代方案是使用即将到来的Async函数。
使用async/await
当Async函数终于落地时,我们将可以采取基于Promise的实现方法并使用它的优点,即像写同步生成器一样写异步。这种做法的另一个好处是你完全不需要再去修改getRandomPonyFooArticle
方法,在它返回一个承诺前,它会一直等待。
要注意的是,await只能在函数中用async
关键字标记后才能使用 它的工作方式和生成器很相似,直到promise完成之前,会在你的上下文中暂停处理。如果等待表达式不是一个promise,它也会被改造成一个promise。
read();
async function read () {
var html = await getRandomPonyFooArticle();
var md = hget(html, {
markdown: true,
root: 'main',
ignore: '.at-subscribe,.mm-comments,.de-sidebar'
});
var txt = marked(md, {
renderer: new Term()
});
console.log(txt);
}
“再次, -- 跟生成器一样 -- 记住,你最好把`await`包装到`try/catch`中,这样你就可以在异步函数中对返回后的promise进行错误捕获和处理。”
此外,一个Async函数总是会返回一个Promise
对象。 这个promise在出现无法捕获的异常时会被拒绝,否则它会处理async
函数的返回值。这就允许我们调用一个async
函数并混入常规的基于promise的扩展。以下例子展示了两个方法的结合(看看Babel的交互式解释器)。
async function asyncFun () {
var value = await Promise
.resolve(1)
.then(x => x * 3)
.then(x => x + 5)
.then(x => x / 2);
return value;
}
asyncFun().then(x => console.log(`x: ${x}`));
// <- 'x: 4'
回到前一个例子中,那表示我们可以从异步读取
函数中返回文本
,并且允许调用者使用promise或另一个Async函数进行扩展。 那样,你的读取
函数将只需关注从Pony Foo上的随机文章中拉取终端可读的Markdown即可。
async function read () {
var html = await getRandomPonyFooArticle();
var md = hget(html, {
markdown: true,
root: 'main',
ignore: '.at-subscribe,.mm-comments,.de-sidebar'
});
var txt = marked(md, {
renderer: new Term()
});
return txt;
}
然后,你可以进一步在另一个Async函数中调用await read()
。
async function write () {
var txt = await read();
console.log(txt);
}
或者你可以只使用promise对象来进一步扩展。
read().then(txt => console.log(txt));
岔路
在异步代码流中,总是能遇到同时执行两个或更多任务的情况。当Async函数更容易编写异步代码后,它们也将自己依次传递给代码。 这就是说:代码在一个时刻只执行一个操作。一个包含多个await
表达式的函数在promise对象执行完之前,在恢复执行和移动到下一个await
表达式之前,会在每个await
表达式处暂停一次, -- 就跟我们在生成器和yield
关键字处观察到的情况一样。
你可以使用Promise.all
来解决创建单个promise对象并进行等待的功能。 当然,最大的问题是从习惯于让所有东西都串行运行改成使用Promise.all
, 否则这将给你的代码带来性能瓶颈。
下面的例子展示了你如何同时完成对三个不同的promise对象进行等待
操作。特定的await
操作符会暂停你的Async
函数,和等待 Promise.all
表达式一起,最终会被解析到一个结果
数组中,我们可以使用析构函数逐个拉取该数组中的单个结果。
async function concurrent () {
var [r1, r2, r3] = await Promise.all([p1, p2, p3]);
}
在某些情况下, 可以用 await *
来改动上述代码片段,让你不必用Promise.all来包装你的promise对象。Babel 5依然支持这种特性,但它已经从规格说明中移除(也已经从Babel 6中移除) -- 因为这些原因
async function concurrent () {
var [r1, r2, r3] = await* [p1, p2, p3];
}
你依然可以用类似all = Promise.all.bind(Promise)
的代码来做些事情,来获得一个简洁的替代Promise.all
的方法。在这之上的是,你可以对Promise.race
做相同的事情,而这跟使用await*
并不等价。
const all = Promise.all.bind(Promise);
async function concurrent () {
var [r1, r2, r3] = `await all([p1, p2, p3])`;
}
错误处理
要注意的是,在异步函数中,错误会被“默默的”吞噬 -- 就像在普通的Promise对象中一样。 除非我们围绕await
表达式添加try/catch
块 -- 而不管在暂停
时,它们会在你的异步
函数体中发生还是在它暂停时发生 -- promise对象会被拒绝并通过Async
函数返回错误。
自然,这可以看作是一个能力: 你可以利用try/catch
代码块,有些东西你无法用回调函数实现-- 但可以用Promise对象实现。 在这种情况下,Async函数就类似生成器
,得益于函数执行暂停特性,你可以利用try/catch
将异步流代码写成同步代码的样子。
此外, 你可以在Async
函数外捕获这些异常, 只需要简单的对它们返回的promise对象添加.catch()
方法调用。在promise对象中尝试用.catch
方法来将try/catch
错误处理组合起来是一种比较灵活的方法,但该方法也可能导致混乱并最终导致错误无法处理。
read()
.then(txt => console.log(txt))
.catch(reason => console.error(reason));
我们要小心谨慎并时刻提醒自己用不同的方法来让我们可以发现错误、处理错误或预防错误。
如今如何使用async/await
如今,有一种在你的代码中使用Async函数的方法是通过Babel。这涉及一系列模块,但只要你愿意,你总是可以拿出一个模块来将全部这些代码包装进去。我包含npm-run
作为一个有用的方法,用于保持本地的所有东西都用包进行安装。
npm i -g npm-run
npm i -D \
browserify \
babelify \
babel-preset-es2015 \
babel-preset-stage-3 \
babel-runtime \
babel-plugin-transform-runtime
echo '{
"presets": ["es2015", "stage-3"],
"plugins": ["transform-runtime"]
}' > .babelrc
在使用babelify
为Async函数提供支持时,以下命令会将example.js
通过browserify
进行编译。然后你就可以用管道将脚本传输给node
执行,或将脚本保存到硬盘中。
npm-run browserify -t babelify example.js | node
深入阅读
Async函数规格草案出奇的短,并且应该能成为一个有趣的读物, 如果你热衷于学习更多这些即将到来的功能。
我已经粘贴了一段代码在下面, 它是为了帮助你理解async
函数的内部是如何工作的。即使我们不可以填充新的关键字,它也可以帮助你理解在async/await
的帷幕后面发生了什么事情。
“换句话说,它应该对学习异步函数内部原理非常有帮助,无论是对生成器还是promise。”
首先,下面的一小段代码展示了一个async函数
如何通过常规的function
关键字来简化声明过程,这将返回一个生成spawn
生成器函数的结果 -- 我们会认为await
在语法上是和yield
等价的。
async function example (a, b, c) {
example function body
}
function example (a, b, c) {
return spawn(function* () {
example function body
}, this);
}
在spawn
中,promise会被代码包装起来并传入生成器函数中,通过用户代码串行的执行,并将值传递到你的“生成器”代码中(async
函数的函数体中)。 在这个意义上,我们可以注意Async函数真的是生成器和primose对象之上的语法糖,这对于让你理解其中每一个环节是如何工作来说非常重要,这是为了让你对于混合、匹配、合并不同的异步代码流的写法有一个更好的理解。
function spawn (genF, self) {
return new Promise(function (resolve, reject) {
var gen = genF.call(self);
step(() => gen.next(undefined));
function step (nextF) {
var next;
try {
next = nextF();
} catch(e) {
// 执行失败,并拒绝promise对象
reject(e);
return;
}
if (next.done) {
// 执行成功,处理promise对象
resolve(next.value);
return;
}
// 未完成,以yield标记的promise对象呗中断,并在此执行step方法
Promise.resolve(next.value).then(
v => step(() => gen.next(v)),
e => step(() => gen.throw(e))
);
}
});
}
js异步请求的更多相关文章
- asp.net mvc放在iis7.5中提示404错误 js异步请求失效解决办法
asp.net mvc中js发请求一般写成: $.get("/Can/index"本地上是没有问题的但是部署到iis上,提示404,正确的请求的路径是:/网站名/Can/index ...
- js异步请求发展史和yield
万恶的回调 对前端工程师来说,异步回调是再熟悉不过了,浏览器中的各种交互逻辑都是通过事件回调实现的,前端逻辑越来越复杂,导致回调函数越来越多,同时 nodejs 的流行也让 javascript 在后 ...
- egg.js异步请求数据
之前已经简单的使用egg-init初始化项目,并创建控制器controller和服务service 在实际项目中, service主要负责数据的请求,并处理(http请求) controll主要负责获 ...
- 原生js 异步请求,responseXML解析
异步更新原理:用XMLHTTP发送请求得到服务器端应答数据,在不重新载入整个页面的情况下,用js操作Dom最终更新页面1.创建XMLHttp请求协议 function createXMLHttpReq ...
- Java爬虫系列四:使用selenium-java爬取js异步请求的数据
在之前的系列文章中介绍了如何使用httpclient抓取页面html以及如何用jsoup分析html源文件内容得到我们想要的数据,但是有时候通过这两种方式不能正常抓取到我们想要的数据,比如看如下例子. ...
- js 异步请求封装
1. function ajax(url, onsuccess) { var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ...
- js异步请求方式
一.使用defer 例: <script src="XXXXXX.js" defer></script> 二.使用promise 例: get('./moc ...
- js 异步请求
<p id="check"> <label>验证码:</label> <input class="vid" id=&q ...
- 将前端js异步调用的多个服务合并为一个前端服务
将前端js异步调用的多个服务合并为一个前端服务 1. 减少前端js异步请求的次数改善浏览体验 2. 方便地针对单个接口做异常降级处理
随机推荐
- [WebGL入门]五,矩阵的基础知识
注:文章译自http://wgld.org/,原作者杉本雅広(doxas),文章中假设有我的额外说明.我会加上[lufy:],另外.鄙人webgl研究还不够深入,一些专业词语,假设翻译有误,欢迎大家指 ...
- EXCEL单元格的获取——多例模式
因为Excel的单元格的行列与单元格是一一相应的,行与列组成的是一对联合主键.给定一个单元格行列或者给定一个单元格名称.须要找到相应的单元格:这样就形成了一种映射关系.须要使用单例模式的变式--多例模 ...
- mac中apache+mysql+php+phpMyAdmin配置备忘
Mac OS X 内置Apache 和 PHP,使用起来非常方便.本文以Mac OS X 10.6.3和为例.主要内容包括: 启动Apache 运行PHP 安装MySQL 使用phpMyAdmin 配 ...
- shell脚本 加密备份MySQL数据库
1.加密备份为.bak文件(实际只是个.zip文件) #!/bin/bash # $:IP地址 # $:用户名 # $:数据库密码 # $:数据库名 # $:加密密码 # $:备份文件名 mysqld ...
- iOS学习笔记(4) — UITableView的 重用机制
iOS学习笔记(4) — UITableView的 重用机制 UITableView中的cell是动态的,在使用过程中,系统会根据屏幕的高度(480)和每个cell的高度计算屏幕中需要显示的cell的 ...
- Python开发【第5节】【函数基础】
1.函数 函数的本质就是功能的封装. 函数的作用 提升代码的重复利用率,避免重复开发相同代码 提高程序开发效率 便于程序维护 2.函数定义 def 函数名(参数): """ ...
- placeholder 占位符
placeholder 简介 | TensorFlow https://tensorflow.google.cn/programmers_guide/low_level_intro 供给 目前来讲 ...
- 【Codevs3151】交通管制I
Position: http://codevs.cn/problem/3151/ List [Codevs3151]交通管制I List Description Input Output Sample ...
- bzoj2989
坐标轴转化+cdq分治 我们发现那个绝对值不太好搞,于是我们把曼哈顿距离转为切比雪夫距离,x'=x-y,y'=x+y,这样两点之间距离就是max(|x1'-x2'|,|y1'-y2'|),这个距离要小 ...
- bzoj3786
splay维护dfs序 我们发现有移动子树这种操作,树剖是做不了了,又要实现子树加,lct又维护不了了,这时我们用splay维护入栈出栈序来支持这些操作.我们记录每个点的入栈时间和出栈时间,这样一个闭 ...