1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00

edit promise/async

This commit is contained in:
Ruan Yifeng 2015-05-11 08:29:21 +08:00
parent e39ea78257
commit 863f5afbee
2 changed files with 132 additions and 55 deletions

View File

@ -444,9 +444,108 @@ run(g);
### 概述
async函数与Promise、Generator函数一样是用来取代回调函数、解决异步操作的另一种方法。它可以写出比Promise和Generator更简洁易读的代码但是依赖这两者来实现。async函数并不属于ES6而是被列入了ES7但是traceur、Babel.js、regenerator等转码器已经支持这个功能转码后立刻就能使用。
async函数与Promise、Generator函数一样是用来取代回调函数、解决异步操作的一种方法。它本质上是Generator函数的语法糖。async函数并不属于ES6而是被列入了ES7但是traceur、Babel.js、regenerator等转码器已经支持这个功能转码后立刻就能使用。
在用法上只要函数名之前加上async关键字就表明该函数内部有异步操作。该异步操作应该返回一个Promise对象前面用await关键字注明。当函数执行的时候一旦遇到await就会先返回等到触发的异步操作完成再接着执行函数体内后面的语句。
下面是一个Generator函数依次读取两个文件。
```javascript
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
```
上面代码中readFile函数是`fs.readFile`的Promise版本。
写成async函数就是下面这样。
```javascript
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
```
一比较就会发现async函数就是将Generator函数的星号*替换成async将yield替换成await仅此而已。
async函数对Generator函数的改进体现在以下三点。
1内置执行器。Generator函数的执行必须靠执行器而async函数自带执行器。也就是说async函数的执行与普通函数一模一样只要一行。
```javascript
var result = asyncReadFile();
```
2更好的语义。async和await比起星号和yield语义更清楚了。async表示函数里有异步操作await表示紧跟在后面的表达式需要等待结果。
3更广的适用性。co函数库约定yield命令后面只能是Thunk函数或Promise对象而async函数的await命令后面可以跟Promise对象和原始类型的值数值、字符串和布尔值但这时等同于同步操作
### 实现
async函数的实现就是将Generator函数和自动执行器包装在一个函数里。
```javascript
async function fn(args){
// ...
}
// 等同于
function fn(args){
return spawn(function*() {
// ...
});
}
```
所有的async函数都可以写成上面的第二种形式其中的spawn函数就是自动执行器。
下面给出spawn函数的实现基本就是前文自动执行器的翻版。
```javascript
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF();
function step(nextF) {
try {
var next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
```
### 用法
同Generator函数一样async函数返回一个Promise对象可以使用then方法添加回调函数。当函数执行的时候一旦遇到await就会先返回等到触发的异步操作完成再接着执行函数体内后面的语句。
下面是一个例子。
```javascript
@ -456,17 +555,19 @@ async function getStockPriceByName(name) {
return stockPrice;
}
getStockPriceByName('goog').then(function (result){
console.log(result);
});
```
上面代码是一个获取股票报价的函数函数前面的async关键字表明该函数内部有异步操作。调用该函数时当遇到await关键字立即返回它后面的表达式getStockSymbol函数产生的Promise对象不再执行函数体内后面的语句。等到getStockSymbol完成再自动回到函数体内执行剩下的语句。
上面代码是一个获取股票报价的函数函数前面的async关键字表明该函数内部有异步操作。调用该函数时会立即返回一个Promise对象
仔细观察getStockPrice函数你会发现除了添加async、await这两个命令整个代码与同步操作的流程一模一样。这就是async函数的本意尽可能地用同步的流程表达异步操作。
实际上上面的例子可以用Generator函数表达。
上面的例子用Generator函数表达就是下面这样。
```javascript
function getStockPriceByName(name) {
return async(function*(name) {
return spawn(function*(name) {
var symbol = yield getStockSymbol(name);
var stockPrice = yield getStockPrice(symbol);
return stockPrice;
@ -474,9 +575,9 @@ function getStockPriceByName(name) {
}
```
上面的例子中,async函数是一个自动任务运行器需要自己定义它的参数是一个Generator函数。async函数的具体实现后文有例子这里只要知道async...await结构本质上是在语言层面提供的一种多个异步任务的自动运行器。
上面的例子中,spawn函数是一个自动执行器由JavaScript引擎内置。它的参数是一个Generator函数。async...await结构本质上是在语言层面提供的异步任务的自动执行器。
下面是一个更一般性的例子。
下面是一个更一般性的例子,指定多少毫秒后输出一个值
```javascript
@ -486,16 +587,20 @@ function timeout(ms) {
});
}
async function asyncValue(value) {
await timeout(50);
return value;
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value)
}
asyncPrint('hello world', 50);
```
上面代码asyncValue函数前面有async关键字表明函数体内有异步操作。执行的时候遇到await语句就会先返回等到timeout函数执行完毕再返回value
上面代码指定50毫秒以后输出“hello world”
总之async函数的关键就是await命令后面必须是一个返回Promise对象的操作。因为Promise对象的运行结果可能是rejected所以最好把await命令放在try...catch代码块中。
### 注意点
await命令后面的Promise对象运行结果可能是rejected所以最好把await命令放在try...catch代码块中。
```javascript
@ -507,6 +612,14 @@ async function myFunction() {
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
};
}
```
await命令只能用在async函数之中如果用在普通函数就会报错。
@ -643,44 +756,7 @@ function chainAnimationsGenerator(elem, animations) {
```
上面代码使用Generator函数遍历了每个动画语义比Promise写法更清晰用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于必须有一个任务运行器自动执行Generator函数上面代码的spawn函数就是任务运行器它返回一个Promise对象而且必须保证yield语句后面的表达式必须返回一个Promise。下面是spawn函数的代码。
```javascript
function spawn(genF) {
// 返回一个Promise
return new Promise(function(resolve, reject) {
// 执行Generator函数返回一个遍历器
var gen = genF();
// 定义一个函数,执行每一个任务
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// 如果任务执行出错Promise状态变为已失败
reject(e);
return;
}
if(next.done) {
// 所有任务执行完毕Promise状态变为已完成
resolve(next.value);
return;
}
// 如果还有下一个任务就继续调用step方法
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
```
上面代码使用Generator函数遍历了每个动画语义比Promise写法更清晰用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于必须有一个任务运行器自动执行Generator函数上面代码的spawn函数就是自动执行器它返回一个Promise对象而且必须保证yield语句后面的表达式必须返回一个Promise。
最后是Async函数的写法。
@ -692,12 +768,12 @@ async function chainAnimationsAsync(elem, animations) {
for(var anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
}
```
可以看到Async函数的实现最简洁最符合语义几乎没有语义不相关的代码。它实际上将Generator写法中的任务运行器改在语言层面提供因此代码量最少。Generator写法的spawn函数本质是将Generator函数转为Promise对象Async函数将这个过程在语言内部处理掉了不暴露给用户
可以看到Async函数的实现最简洁最符合语义几乎没有语义不相关的代码。它将Generator写法中的自动执行器改在语言层面提供不暴露给用户因此代码量最少。如果使用Generator写法自动执行器需要用户自己提供

View File

@ -63,6 +63,7 @@
- Mozilla Developer Network, [Iterators and generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)
- Mozilla Developer Network, [The Iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/The_Iterator_protocol)
- Jason Orendorff, [ES6 In Depth: Iterators and the for-of loop](https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/): 遍历器与for...of循环的介绍
- Matt Baker, [Replacing callbacks with ES6 Generators](http://flippinawesome.org/2014/02/10/replacing-callbacks-with-es6-generators/)
- Steven Sanderson, [Experiments with Koa and JavaScript Generators](http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/)
- jmar777, [What's the Big Deal with Generators?](http://devsmash.com/blog/whats-the-big-deal-with-generators)