ES2017 标准引入了 async 函数,使得异步操做变得更加方便。javascript
async 函数是什么?一句话,它就是 Generator 函数的语法糖。html
前文有一个 Generator 函数,依次读取两个文件。前端
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
复制代码
上面代码的函数gen
能够写成async
函数,就是下面这样。java
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
复制代码
一比较就会发现,async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已。node
async
函数对 Generator 函数的改进,体如今如下四点。webpack
(1)内置执行器。git
Generator 函数的执行必须靠执行器,因此才有了co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数如出一辙,只要一行。github
asyncReadFile();
复制代码
上面的代码调用了asyncReadFile
函数,而后它就会自动执行,输出最后结果。这彻底不像 Generator 函数,须要调用next
方法,或者用co
模块,才能真正执行,获得最后结果。web
(2)更好的语义。ajax
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操做,await
表示紧跟在后面的表达式须要等待结果。
(3)更广的适用性。
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,能够是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成当即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你能够用then
方法指定下一步的操做。
进一步说,async
函数彻底能够看做多个异步操做,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
async
函数返回一个 Promise 对象,可使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操做完成,再接着执行函数体内后面的语句。
下面是一个例子。
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
复制代码
上面代码是一个获取股票报价的函数,函数前面的async
关键字,代表该函数内部有异步操做。调用该函数时,会当即返回一个Promise
对象。
下面是另外一个例子,指定多少毫秒后输出一个值。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
复制代码
上面代码指定 50 毫秒之后,输出hello world
。
因为async
函数返回的是 Promise 对象,能够做为await
命令的参数。因此,上面的例子也能够写成下面的形式。
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
复制代码
async 函数有多种使用形式。
// 函数声明
async function foo() { return 1}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
复制代码
async
函数的语法规则整体上比较简单,难点是错误处理机制。
async
函数返回一个 Promise 对象。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
复制代码
上面代码中,函数f
内部return
命令返回的值,会被then
方法回调函数接收到。
@
async function f() {
await 'hello world';
}
f().then(v => console.log(v))
// undefined
// Promise {<resolved>: undefined}
// 上面的例子 没有return 因此是undefined
@
async function f() {
a=await 'hello world';
return a
}
f().then(v => console.log(v))
// hello world
//Promise {<resolved>: undefined}
@
async function f() {
return await 'hello world';
}
f().then(v => console.log(v))
// hello world
// Promise {<resolved>: undefined}
@
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// hello world
// Promise {<resolved>: undefined}@
async function f() {
return setTimeout(()=>{console.log('hello world')});
}
f().then(v => console.log(v))
// 102
// Promise {<resolved>: undefined}
// hello world复制代码
async
函数内部抛出错误,会致使返回的 Promise 对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error: 出错了
复制代码
async
函数返回的 Promise 对象,必须等到内部全部await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操做执行完,才会执行then
方法指定的回调函数。
下面是一个例子。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
复制代码
上面代码中,函数getTitle
内部有三个操做:抓取网页、取出文本、匹配页面标题。只有这三个操做所有完成,才会执行then
方法里面的console.log
。
正常状况下,await
命令后面是一个 Promise 对象,返回该对象的结果。若是不是 Promise 对象,就直接返回对应的值。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
复制代码
上面代码中,await
命令的参数是数值123
,这时等同于return 123
。
另外一种状况是,await
命令后面是一个thenable
对象(即定义then
方法的对象),那么await
会将其等同于 Promise 对象。
class Sleep {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) {
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}
(async () => {
const sleepTime = await new Sleep(1000);
console.log(sleepTime);
})();
// 1000
复制代码
上面代码中,await
命令后面是一个Sleep
对象的实例。这个实例不是 Promise 对象,可是由于定义了then
方法,await
会将其视为Promise
处理。
这个例子还演示了如何实现休眠效果。JavaScript 一直没有休眠的语法,可是借助await
命令就可让程序停顿指定的时间。下面给出了一个简化的sleep
实现。
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}
// 用法
async function one2FiveInAsync() {
for(let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000);
}
}
one2FiveInAsync();
复制代码
await
命令后面的 Promise 对象若是变为reject
状态,则reject
的参数会被catch
方法的回调函数接收到。
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
复制代码
注意,上面代码中,await
语句前面没有return
,可是reject
方法的参数依然传入了catch
方法的回调函数。这里若是在await
前面加上return
,效果是同样的。
任何一个await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
复制代码
上面代码中,第二个await
语句是不会执行的,由于第一个await
语句状态变成了reject
。
有时,咱们但愿即便前一个异步操做失败,也不要中断后面的异步操做。这时能够将第一个await
放在try...catch
结构里面,这样无论这个异步操做是否成功,第二个await
都会执行。
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
复制代码
另外一种方法是await
后面的 Promise 对象再跟一个catch
方法,处理前面可能出现的错误。
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hello world
复制代码
若是await
后面的异步操做出错,那么等同于async
函数返回的 Promise 对象被reject
。
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
复制代码
上面代码中,async
函数f
执行后,await
后面的 Promise 对象会抛出一个错误对象,致使catch
方法的回调函数被调用,它的参数就是抛出的错误对象。具体的执行机制,能够参考后文的“async 函数的实现原理”。
防止出错的方法,也是将其放在try...catch
代码块之中。
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
} catch(e) {
}
return await('hello world');
}
复制代码
若是有多个await
命令,能够统一放在try...catch
结构中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
复制代码
下面的例子使用try...catch
结构,实现屡次重复尝试。
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
复制代码
上面代码中,若是await
操做成功,就会使用break
语句退出循环;若是失败,会被catch
语句捕捉,而后进入下一轮循环。
第一点,前面已经说过,await
命令后面的Promise
对象,运行结果多是rejected
,因此最好把await
命令放在try...catch
代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另外一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
复制代码
@错误处理的更优雅的方式:
你是否会为了系统健壮性,亦或者是为了捕获异步的错误,而频繁的在 async 函数中写 try/catch 的逻辑?
async function func() {
try {
let res = await asyncFunc()
} catch (e) {
//......
}
}复制代码
这样咱们就可使用一个辅助函数包裹这个 async 函数实现错误捕获
async function func() {
let [err, res] = await errorCaptured(asyncFunc)
if (err) {
//... 错误捕获
}
//...
}
复制代码
可是这么作有一个缺陷就是每次使用的时候,都要引入 errorCaptured 这个辅助函数,有没有“懒”的方法呢?
答案确定是有的,我在那篇博客后提出了一个新的思路,能够经过一个 webpack loader 来自动注入 try/catch 代码,最后的结果但愿是这样的
// development
async function func() {
let res = await asyncFunc()
//...其余逻辑
}
// release
async function func() {
try {
let res = await asyncFunc()
} catch (e) {
//......
}
//...其余逻辑
}
复制代码
是否是很棒?在开发环境中不须要任何多余的代码,让 webpack 自动给生产环境的代码注入错误捕获的逻辑,此处是经过ast来处理,上方是处理以前和以后的代码。
第二点,多个await
命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
复制代码
上面代码中,getFoo
和getBar
是两个独立的异步操做(即互不依赖),被写成继发关系。这样比较耗时,由于只有getFoo
完成之后,才会执行getBar
,彻底可让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
复制代码
上面两种写法,getFoo
和getBar
都是同时触发,这样就会缩短程序的执行时间。
第三点,await
命令只能用在async
函数之中,若是用在普通函数,就会报错。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
复制代码
上面代码会报错,由于await
用在普通函数之中了。可是,若是将forEach
方法的参数改为async
函数,也有问题。
function dbFuc(db) { //这里不须要 async
let docs = [{}, {}, {}];
// 可能获得错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
复制代码
上面代码可能不会正常工做,缘由是这时三个db.post
操做将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for
循环。
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
复制代码
若是确实但愿多个请求并发执行,可使用Promise.all
方法。当三个请求都会resolved
时,下面两种写法效果相同。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
// 或者使用下面的写法
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
复制代码
第四点,async 函数能够保留运行堆栈。
const a = () => {
b().then(() => c());
};
复制代码
上面代码中,函数a
内部运行了一个异步任务b()
。当b()
运行的时候,函数a()
不会中断,而是继续执行。等到b()
运行结束,可能a()
早就运行结束了,b()
所在的上下文环境已经消失了。若是b()
或c()
报错,错误堆栈将不包括a()
。
如今将这个例子改为async
函数。
const a = async () => {
await b();
c();
};
复制代码
上面代码中,b()
运行的时候,a()
是暂停执行,上下文环境都保存着。一旦b()
或c()
报错,错误堆栈将包括a()
。
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
复制代码
全部的async
函数均可以写成上面的第二种形式,其中的spawn
函数就是自动执行器。
下面给出spawn
函数的实现,基本就是前文自动执行器的翻版。
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
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); });
});
}
复制代码
咱们经过一个例子,来看 async 函数与 Promise、Generator 函数的比较。
假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。若是当中有一个动画出错,就再也不往下执行,返回上一个成功执行的动画的返回值。
首先是 Promise 的写法。
function chainAnimationsPromise(elem, animations) {
// 变量ret用来保存上一个动画的返回值
let ret = null;
// 新建一个空的Promise
let p = Promise.resolve();
// 使用then方法,添加全部动画
for(let anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
// 返回一个部署了错误捕捉机制的Promise
return p.catch(function(e) {
/* 忽略错误,继续执行 */
}).then(function() {
return ret;
});
}
复制代码
虽然 Promise 的写法比回调函数的写法大大改进,可是一眼看上去,代码彻底都是 Promise 的 API(then
、catch
等等),操做自己的语义反而不容易看出来。
接着是 Generator 函数的写法。
function chainAnimationsGenerator(elem, animations) {
return spawn(function*() {
let ret = null;
try {
for(let anim of animations) {
ret = yield anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
});
}
复制代码
上面代码使用 Generator 函数遍历了每一个动画,语义比 Promise 写法更清晰,用户定义的操做所有都出如今spawn
函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的spawn
函数就是自动执行器,它返回一个 Promise 对象,并且必须保证yield
语句后面的表达式,必须返回一个 Promise。
最后是 async 函数的写法。
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
}
复制代码
能够看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,所以代码量最少。若是使用 Generator 写法,自动执行器须要用户本身提供。
实际开发中,常常遇到一组异步操做,须要按照顺序完成。好比,依次远程读取一组 URL,而后按照读取的顺序输出结果。
Promise 的写法以下。
function logInOrder(urls) {
// 远程读取全部URL
const textPromises = urls.map(url => {
return fetch(url).then(response => response.text());
});
// 按次序输出
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(text));
}, Promise.resolve());
}
复制代码
上面代码使用fetch
方法,同时远程读取一组 URL。每一个fetch
操做都返回一个 Promise 对象,放入textPromises
数组。而后,reduce
方法依次处理每一个 Promise 对象,而后使用then
,将全部 Promise 对象连起来,所以就能够依次输出结果。
这种写法不太直观,可读性比较差。下面是 async 函数实现。
async function logInOrder(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}
复制代码
上面代码确实大大简化,问题是全部远程操做都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样作效率不好,很是浪费时间。咱们须要的是并发发出远程请求。
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
复制代码
上面代码中,虽然map
方法的参数是async
函数,但它是并发执行的,由于只有async
函数内部是继发执行,外部不受影响。后面的for..of
循环内部使用了await
,所以实现了按顺序输出。
根据语法规格,await
命令只能出如今 async 函数内部,不然都会报错。
// 报错
const data = await fetch('https://api.example.com');
复制代码
上面代码中,await
命令独立使用,没有放在 async 函数里面,就会报错。
目前,有一个语法提案,容许在模块的顶层独立使用await
命令。这个提案的目的,是借用await
解决模块异步加载的问题。
// awaiting.js
let output;
async function main() {
const dynamic = await import(someMission);
const data = await fetch(url);
output = someProcess(dynamic.default, data);
}
main();
export { output };
复制代码
上面代码中,模块awaiting.js
的输出值output
,取决于异步操做。咱们把异步操做包装在一个 async 函数里面,而后调用这个函数,只有等里面的异步操做都执行,变量output
才会有值,不然就返回undefined
。
上面的代码也能够写成当即执行函数的形式。
// awaiting.js
let output;
(async function main() {
const dynamic = await import(someMission);
const data = await fetch(url);
output = someProcess(dynamic.default, data);
})();
export { output };
复制代码
下面是加载这个模块的写法。
// usage.js
import { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
复制代码
上面代码中,outputPlusValue()
的执行结果,彻底取决于执行的时间。若是awaiting.js
里面的异步操做没执行完,加载进来的output
的值就是undefined
。
目前的解决方法,就是让原始模块输出一个 Promise 对象,从这个 Promise 对象判断异步操做有没有结束。
// awaiting.js
let output;
export default (async function main() {
const dynamic = await import(someMission);
const data = await fetch(url);
output = someProcess(dynamic.default, data);
})();
export { output };
复制代码
上面代码中,awaiting.js
除了输出output
,还默认输出一个 Promise 对象(async 函数当即执行后,返回一个 Promise 对象),从这个对象判断异步操做是否结束。
下面是加载这个模块的新的写法。
// usage.js
import promise, { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }
promise.then(() => {
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
});
复制代码
上面代码中,将awaiting.js
对象的输出,放在promise.then()
里面,这样就能保证异步操做完成之后,才去读取output
。
这种写法比较麻烦,等于要求模块的使用者遵照一个额外的使用协议,按照特殊的方法使用这个模块。一旦你忘了要用 Promise 加载,只使用正常的加载方法,依赖这个模块的代码就可能出错。并且,若是上面的usage.js
又有对外的输出,等于这个依赖链的全部模块都要使用 Promise 加载。
顶层的await
命令,就是为了解决这个问题。它保证只有异步操做完成,模块才会输出值。
// awaiting.js
const dynamic = import(someMission);
const data = fetch(url);
export const output = someProcess((await dynamic).default, await data);
复制代码
上面代码中,两个异步操做在输出的时候,都加上了await
命令。只有等到异步操做完成,这个模块才会输出值。
加载这个模块的写法以下。
// usage.js
import { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
复制代码
上面代码的写法,与普通的模块加载彻底同样。也就是说,模块的使用者彻底不用关心,依赖模块的内部有没有异步操做,正常加载便可。
这时,模块的加载会等待依赖模块(上例是awaiting.js
)的异步操做完成,才执行后面的代码,有点像暂停在那里。因此,它老是会获得正确的output
,不会由于加载时机的不一样,而获得不同的值。
下面是顶层await
的一些使用场景。
// import() 方法加载
const strings = await import(`/i18n/${navigator.language}`);
// 数据库操做
const connection = await dbConnector();
// 依赖回滚
let jQuery;
try {
jQuery = await import('https://cdn-a.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.com/jQuery');
}
复制代码
注意,若是加载多个包含顶层await
命令的模块,加载命令是同步执行的。
// x.js
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");
// y.js
console.log("Y");
// z.js
import "./x.js";
import "./y.js";
console.log("Z");
复制代码
上面代码有三个模块,最后的z.js
加载x.js
和y.js
,打印结果是X1
、Y
、X2
、Z
。这说明,z.js
并无等待x.js
加载完成,再去加载y.js
。
顶层的await
命令有点像,交出代码的执行权给其余的模块加载,等异步操做完成后,再拿回执行权,继续向下执行。
Generator 函数是一个普通函数,可是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不一样的内部状态(yield
在英语里的意思就是“产出”)。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();复制代码
上面代码定义了一个 Generator 函数helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
而后,Generator 函数的调用方法与普通函数同样,也是在函数名后面加上一对圆括号。不一样的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法能够恢复执行。
上面代码一共调用了四次next
方法。
第一次调用,Generator 函数开始执行,直到遇到第一个yield
表达式为止。next
方法返回一个对象,它的value
属性就是当前yield
表达式的值hello
,done
属性的值false
,表示遍历尚未结束。
第二次调用,Generator 函数从上次yield
表达式停下的地方,一直执行到下一个yield
表达式。next
方法返回的对象的value
属性就是当前yield
表达式的值world
,done
属性的值false
,表示遍历尚未结束。
第三次调用,Generator 函数从上次yield
表达式停下的地方,一直执行到return
语句(若是没有return
语句,就执行到函数结束)。next
方法返回的对象的value
属性,就是紧跟在return
语句后面的表达式的值(若是没有return
语句,则value
属性的值为undefined
),done
属性的值true
,表示遍历已经结束。
第四次调用,此时 Generator 函数已经运行完毕,next
方法返回对象的value
属性为undefined
,done
属性为true
。之后再调用next
方法,返回的都是这个值。
总结一下,调用 Generator 函数,返回一个遍历器对象,表明 Generator 函数的内部指针。之后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
ES6 没有规定,function
关键字与函数名之间的星号,写在哪一个位置。这致使下面的写法都能经过。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
复制代码
因为 Generator 函数仍然是普通函数,因此通常的写法是上面的第三种,即星号紧跟在function
关键字后面。
因为 Generator 函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,因此其实提供了一种能够暂停执行的函数。yield
表达式就是暂停标志。
遍历器对象的next
方法的运行逻辑以下。
(1)遇到yield
表达式,就暂停执行后面的操做,并将紧跟在yield
后面的那个表达式的值,做为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)若是没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,做为返回的对象的value
属性值。
(4)若是该函数没有return
语句,则返回的对象的value
属性值为undefined
。
须要注意的是,yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,所以等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
function* gen() {
yield 123 + 456;
}
复制代码
上面代码中,yield
后面的表达式123 + 456
,不会当即求值,只会在next
方法将指针移到这一句时,才会求值。
ajax的异步处理
function* main() {
var result = yield request("http://www.filltext.com?rows=10&f={firstName}");
console.log(result);
//do 别的ajax请求;
}复制代码
一、co 模块,它基于 ES6 的 generator 和 yield ,让咱们能用同步的形式编写异步代码的nodejs模块。
二、co 模块是能让咱们以同步的形式编写异步代码的 nodejs 模块
三、学习网络地址:https://segmentfault.com/a/1190000002732081
四、代码以下:
var co = require ('co');
co(function*() {
执行代码。。。
});复制代码
co 模块能够将异步解放成同步。co 函数接受一个 generator 函数做为参数,在函数内部自动执行 yield 。co 函数接受一个 generator 函数,而且在 co 函数内部执行,生成一个 generator 实例。调用 generator 的 next 方法, 对生成的对象的 value 属性值使用 toPromise 方法,生成一个 promise 实例,当这个 promise 实例的状态变为 resolved 时,执行 onFulfilled 方法,再次对 generator 实例执行 next 方法,而后重复整个过程。若是出现错误,则执行这个 promise 实例定义的 reject 函数即 onRejected 方法。
javascript中的thunk函数就是一个单参数函数,且该参数必须是一个callback函数,callback的签名必须为callback(err,args...);
所谓的thunkify就是将一个多参数函数转化为一个thunk函数,该多参数函数必须有一个callback做为参数
next
方法,就会返回一个有着value
和done
两个属性的对象async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
async function foo() { return 1}复制代码
以上async函数虽然有返回值,可是,其实真正的返回值并不是所看到的那样,而是被 promise 包装过的返回值。
async function a() {
return await 2;
}
a(); // Promise {<resolved>: 2}
a().then(function (result) {
console.log(result);return 3
})
.then((data)=>console.log(data));
// 2
// 3
// Promise {<resolved>: undefined}复制代码
感谢阮一峰老师的付出,本文主要内容来源于阮一峰老师博客,特此说明。