本文是基于阮一峰老师《es6入门教程》异步编程几个章节的学习笔记,包括Iterator
、Promise
、Generator
、Async/Await
、异步Generator
几个章节的知识总结,改写及加入了一些案例,以练来学,融入了一些自己的思考和拓展。
ES6之前,异步编程大约有一下四种
- 回调函数
- 事件监听
- 发布/订阅
- 基于
Promise
对象的社区方案bluebird
、Q
、When
ES6 提供了三种方案Promise
Generator
Async/Await
特点
pending
fulfilled
rejected
, 状态不受外界影响缺点
jsconst p = new Promise((resolve, reject) => {
// 同步执行
// 刚开始是pending状态
resolve(123); // 将变成fullfilled状态
// setTimeout(resolve, 1000); // 无效 一旦改变状态就不会再变
// resolve或reject之后不应有代码,应写到.then里面
});
p.then((val) => {
// 异步执行 微任务
console.log(val);
});
const p2 = new Promise(() => {
console.log(a+3); // 报错
resolve();
})
console.log('promise内部的错误不会反应到外部');
.then
的第二个参数.catch
推荐用这个方案, 能够捕捉前面所有报错window.addEventListener('unhandledrejection')
process.on('unhandledrejection', cb)
.catch
后面返回一个fullfiled
状态的promise
可以继续使用.then
方法.catch
方法.then
.catch
是原型方法jsconst myFinally = () => {
// todo something
}
Promise.prototype.then(
(arg) => {
myFinally();
return Promise.resolve(arg)
},
(arg) => {
myFinally();
return Promise.reject(arg)
},
);
Promise
实例,包装成一个新的 Promise
实例fullfilled
状态,包装实例才会变成fullfilled
状态rejected
状态,包装实例就会变成rejected
状态jsfunction createPromise(val, ms = 100, isReject = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
isReject ? reject(val) : resolve(val);
}, ms)
})
}
Promise.all([
createPromise(1),
createPromise(2, 200),
]).then((val) => {
console.log(val); // [1 ,2]
})
Promise.all([
createPromise(1,10, true),
createPromise(2, 200),
]).catch((val) => {
// 10ms后执行这里
console.log(val); // 1
})
jsPromise.allSettled([
createPromise(1,10, true),
createPromise(2, 200, true),
]).then((val) => {
console.log(val); // [{reason: 1, status: "rejected"}, {...}]
})
fullfilled
状态,包装实例就会变成fullfilled
状态Promise
Promise
实例,将原封不动地返回这个实例thenable
对象,会转换为Promise
对象,然后执行then
方法Promise
实例即可rejected
状态的Promise
实例(async () => f())()
promise.catch
jsfunction getJSON (options) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.methods || 'GET', options.url, false);
xhr.onreadystatechange = function() {
// 0 未初始化, 还没有调用open
// 1 已建立连接还没有调用send
// 2 请求已接收,正在处理中
// 3 请求处理中. 响应中已有部分数据了
// 4 请求已经完成
if(xhr.readyState === 4) {
if(xhr.status >=200 && xhr.status<300 || xhr === 304) {
resolve(xhr.responseText)
} else {
reject(xhr.responseText)
}
}
}
xhr.setRequestHeader('Accept', 'application/json');
// xhr.responseType = 'json'
let data = undefined;
if(options.methods && options.methods !== 'GET') {
data = JSON.stringify(options.data);
}
xhr.send(data)
})
}
jsPromise.myAll = function(arr) {
return new Promise((resolve, reject) => {
const results = Array(arr.length);
let count = 0;
arr.forEach((inst, index) => {
inst.then(res => {
++count;
results[index] = res;
if(count === arr.length) {
resolve(results)
}
}).catch(reject);
});
});
}
js// 串行实现
const promiseChain = (promises) => {
promises.reduce((sum, p) => {
return sum.then(() => p())
}, Promise.resolve());
}
// 测试用例
genPromises(10);
promiseChain(genPromises(10));
// 辅助代码
function genPromises(num) {
return Array.from(
{length: num},
(i, index) => () => new Promise(
(resolve) => {
const time = Math.random() * 1000
setTimeout(() => {
console.log(index, time);
resolve()
}, time);
}
)
);
}
jsrun(genPromises(10), 2);
function run(promises, num) {
Array.from({length: num}).forEach(async () => {
while(promises.length) {
const p = promises.shift();
await p();
}
});
}
run2(genPromises(10), 2);
function run2(promises, num) {
function exec(p) {
if(!p) return;
p().then(() => exec(promises.shift()))
}
Array.from({length: num}).forEach(async () => {
exec(promises.shift())
});
}
jsPromise.abort = function(p) {
let abort;
const pAbort = new Promise((resolve, reject) => {
abort = reject;
});
const result = Promise.race([pAbort, p]);
result.abort = abort;
return result;
}
const p1 = Promise.abort(createPromise(22, 3000));
p1.then(console.log).catch();
setTimeout(() => {
p1.abort()
}, 2000);
由于异步编程
Generator
依赖Iterator
,所以暂且把Iterator
也归类到异步编程中吧
Iterator的作用
扩展运算符
、for...of
进行遍历jsfunction makeIterator(obj){
const keys = Reflect.ownKeys(obj);
obj[Symbol.iterator] = function (){
let nextIndex = 0;
return {
next() {
return nextIndex < keys.length ? ({
value: obj[keys[nextIndex++]], done: false
}) : ({
value: false, done: true
})
}
}
}
return obj
}
// 测试
var a = makeIterator({a:1});
[...a]; // ok
for(let i of a){ console.log(i) }; // ok
js// 注: 扩展运算符只能近似模拟, 模拟是为了了解它的执行细节
function kb(obj) {
const g = obj[Symbol.iterator]();
const result = [];
let item = g.next();
while(!item.done) {
result.push(item.value);
item = g.next();
}
return result;
}
kb(makeIterator({a:1,b:2}));
kb([1,2]);
var obj1 = {0:1,1:'a', length:2};
obj1[Symbol.iterator] = Array.prototype[Symbol.iterator];
kb(obj1)
// 模拟 for...of
function forOf(objI, cb) {
const g = objI[Symbol.iterator]();
let item = g.next();
while(!item.done) {
cb(item.value);
item = g.next();
}
}
forOf([1,2,3], console.log);
Object.keys
Object.entries
Iterator
接口 obj[Symbole.iterator] = function * gen(){}
for...of
只有实现了Iterator
接口才能遍历,如对象就不能遍历;for...in
是遍历对象的属性名for...in
不适合遍历数组for...in
还会遍历原型链上可枚举属性for...in
循环会以任意顺序遍历键名forEach
是对for...in
的封装,使用更简便,forEach
的缺点
break
return
continue
)async
await
)Generator
也就是协程
协程可以理解为跑在线程上的任务,一种比线程更轻量级的存在
一个线程可以有多个协程,单个线程只能同时运行一个协程
协程不被操作系统内核所管理,完全由程序控制。好处是不像线程切换那样消耗资源
从语法上看,Generator
是一种状态机,封装了多个内部状态
*
位置随意next()
不能传递参数问题Generator
函数的声明与函数声明语句有一样的特性,即声明提前、函数体提前, 可以重复声明。js// function关键字与函数名之间有一个星号 * 位置你随意
function* createGenerator(){
// 函数体内部使用yield表达式
yield 'hello';
yield 'world';
return 'ending';
}
const g = createGenerator(); // g 遍历器对象
g.next(); // {value: 'hello', done: false}
g.next(); // {value: 'world', done: false}
g.next(); // {value: 'ending', done: true}
g.next(); // {value: undefined, done: true}
// yield 表达式 暂停的标志
function * gen(){
// 只有调用next方法,下面的语句才会执行,
// 相当于JS提供了惰性求值的心语法
yield console.log('exced'), 123 + 234;
}
const g2 = gen(); // 没有打印
g2.next(); // 打印 'exced' // 返回 {value: 357, done: false}
// generator提供了一种与外界实时通信的机制
// 举个例子:计算矩形的周长
function * rectGen() {
// console.log('第一次调用next()时执行,无法传参数');
const width = (yield) || 0; // 第二次调用next()执行,支持传参数,这里赋值给width
const height = (yield) || 0;
yield;
return 2*width + 2*height;
}
// 解决第一调用不能传参的问题
function wrapGen(gen) {
const g = gen();
g.next();
return g;
}
const rect = wrapGen(rectGen);
rect.next(10); // 传入宽
rect.next(20); // 传入高
const perimeter = rect.next().value; // 计算
console.log('矩形周长', perimeter); // 60
执行顺序
jsfunction *gen() {
console.log('start');
yield 1;
console.log('end');
}
const g = gen();
console.log(g.next().value);
console.log(g.next().value);
console.log('同步代码');
// 'start' 1 'end' '同步代码'
由于学习Generator
时,有一些奇怪的现象,于是尝试探寻了下Generator
的原型链, 先来说一下我的疑惑,在讲解前,假设你已经熟悉JavaScript
原型链(如不熟悉请点击这里)。
jsfunction * gen(){}
var g = gen();
g.__proto__ === gen.prototype; // true
// 这是不是很像new吗?
g = new gen(); // 报错 , 为什么不让new?
// this问题
function * gen(){
this.aa = 1;
}
var g = gen();
g.next();
g.aa; // undefined
window.aa // 1 // 发现跑到window下面了,严格模式下会报错
/* 为什么 不存在实例属性aa?
这个也许是 不让 new 的原因,避免当成new理解
*/
// 那么怎样解决this问题呢,阮一峰老师的es6一书给了两个方案
// 1. 使用call
var g = gen.call(obj);
// 这样是解决了this问题,但是使用实例属性要用obj 而不是g
// 2. 绑定到gen.prototype不就行了?
var g = gen.call(gen.prototype);
g.next();
g.aa; // 1
// 貌似解决了问题,但是JS原型是共享了,这样会污染原型
var g2 = gen();
g2.aa; // 1
因此,我绘制了Generator
的原型图
简单总结一下
GeneratorFunction
是在Function
的基础上包装了一层, 只是这个对象没有暴露出来
jsfunction * gen(){}
const GF = gen.constructor // gen.__proto__.constructor
const gf = new GF('yield "hello"'); // 这是不是和 new Function 很像
gf().next();
如何理解g[Symbol.iterator]() === g
?
Reflect.ownKeys(g)
检测 g
并没有[Symbol.iterator]
属性, 见过层层检查原型链, 最终在 Reflect.ownKeys(g.__proto__.__proto__.__proto__)
检测到[Symbol.iterator]
属性g[Symbol.iterator]()
g.__proto__.__proto__.__proto__[Symbole.iterator].call(g)
Generator.__proto__[Symbole.iterator].call(g)
Generator[Symbole.iterator].call(g)
Generator[Symbole.iterator].call(obj) === obj
,Generator[Symbole.iterator]
函数会返回当前this
Symbol.iterator
属性是一个高阶函数,高阶函数返回的对象必须有next()
方法,这个函数就是生成器函数function * gen(){}
用来生成一个遍历器对象var g = gen()
,也就是g[Symbol.iterator]
是满足上述要求的高阶函数,即生成器函数
g
是遍历器对象; g[Symbol.iterator]()
也是遍历器对象,它们的遍历结果必须一样,那么它们的关系是怎样的呢?g === g[Symbol.iterator]()
{value: undefined, done: true}
jsfunction *genT() {
console.log(0, '启动');
try{
yield 1;
console.log('一', '抛出错误,后面的代码不会执行'); // 这里不会打印
}catch(e){
console.log('进入catch', e);
console.log('二', 'g.throw()会附带执行一条语句')
yield 2;
console.log('三'); // 这里不会打印
}
finally{
yield 'finally';
}
}
var gt = genT();
try {
console.log(gt.next()); // {value: 1, done: false}
console.log(gt.throw(123)); // {value: 2, done: false}
console.log(gt.throw(456)); // {value: 'finally', done: true}
console.log('因为finally存在,yield暂停了错误,外部代码回继续执行');
} catch(e) {
console.log('再次抛出错误会被外部捕获', e);
}
console.log(gt.next()); // Uncaught 456 // 释放错误,终止外部代码运行
console.log('这里不会打印');
Generator
函数运行, 并返回值{done:true, value: 'returnVal'}
。可以理解为把当前yield
语句替换为return
语句Generator
函数内部有finally
语句, 会进入finally
语句,执行到下一个yield
语句并返回。jsfunction *numbers2(){
try{
yield 1;
console.log('这里不会执行');
} finally {
console.log('finally');
yield 2;
console.log('done');
return 123;
}
}
var gn2 = numbers2();
gn2.next(); // 进入try语句 // 返回{ value: 1, done: false }
gn2.return('a'); // 打印finally, 然后后返回{ value: 2, done: false }
gn2.next(); // 打印done 然后返回 {value: 123, done: true}
Generator
函数里执行另一个Generator
函数for...of
展开另一个生成器到当前生成器yield*
案例 拉平数组
与 普通方式比较 好像 yield*
更好理解一些。
Generator
是“半协程” 只有Generator
的调用者才能将程序的执行权还给 Generator 函数
Generator
与上下文 yield
语句是保存当前上下文,暂时退出,等next()
调用时在回复js// ajax
function * main(){
const res = yield request('test.json');
console.log(res)
}
const it = main();
it.next();
function request(url){
makeAjaxCall(url, it.next);
}
// 文件逐行读取
function *numbers(){
const file = new FileReader('numbers.txt');
try {
while(file.eof) {
yield parseInt(file.readLine(), 10)
}
} finally {
file.close();
}
}
Thunk函数, 惰性求值, 本质是将参数编程函数来模拟惰性求值
jsvar Trunk = function (fn) {
return function (...args) {
return (cb) => fn.call(this, ...args, cb);
};
};
jsfunction createPromise(val, ms = 1000, isReject = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// console.log(val);
(isReject ? reject : resolve)(val);
}, ms);
});
}
function * gen(){
console.log('start');
yield createPromise(1,1000);
console.log(1);
yield createPromise(2, 2000);
console.log('end');
}
function run(gen) {
const g = gen();
next();
function next(val) {
const res = g.next(val);
if(res.done) return;
res.value.then(next);
}
}
run(gen);
jsfunction * gen() {
console.log('start');
const a = yield createPromise(1,1000);
// // 如果报错会附带执行下一条语句
// const a = yield createPromise(1,1000, 1);
console.log(1);
const b = yield createPromise(2, 2000);
console.log('end');
return a+b*2;
}
co(gen).then(console.log) // 5
function co(gen) {
var ctx = this;
return new Promise((resolve, reject) => {
if(typeof gen === "function") {
gen = gen.call(ctx);
}
if(!gen && typeof gen !== 'function' ||
typeof gen.next !== "function") {
return resolve(gen)
};
onFillfilled();
function onFillfilled(res) {
let ret
try {
ret = gen.next(res);
} catch(e) {
reject(e)
}
next(ret)
}
function next(ret) {
if(ret.done) return resolve(ret.value);
ret.value.then(onFillfilled, onReject)
}
function onReject(res) {
try{
gen.throw(res);
} catch(e){
reject(e);
}
}
})
}
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
Promise
。jsasync function f1(){}
var a = f1();
// async 函数返回值是Promise
Reflect.toString.call(a); // '[object Promise]'
a.then(console.log); // undefined
async function f2(){
12+12n;
}
f2();
console.log('asyn函数报错,不会中断外部代码的执行');
Promise
对象,会等待然后返回该对象的结果thenable
对象, 会转换为promise
,执行then
方法然后返回结果.catch
try...catch
await
后面的语句代码块是在微任务中执行jsasync function fn(){
console.log('start');
let res1 = await createPromise(1,1000);
// co中Promise报错会附带执行一条语句,
// async函数中不会,会直接把错误结果返回
// let res1 = await createPromise(1,1000, 1);
console.log('1', res1);
await createPromise(2,1000);
console.log('end');
}
fn();
使用注意事项:
try catch
捕捉Promise
错误await
语句,如果不存在继发关系,最好让它们并行 Promise.all
await
不能出现在普通函数中await
在数组循环中 无效async
函数可以保留错误栈obj[Symbol.syncIterator]().next()
返回值是Promise<{value: any, done:boolean}>
for await...of
遍历 异步遍历器对象Generator
yield *
后面可以跟异步遍历器对象jsf();
async function f() {
// function * gen(){ // 写async回编程同步执行
async function * gen(){
console.log('start');
// yield createPromise(1, 2000); // 等价于下面的方式
yield await createPromise(1, 2000);
console.log(1);
yield createPromise(2, 200);
yield createPromise(3, 1200);
console.log('end');
}
const g = gen();
return await taskAsync(g);
}
async function taskAsync(iterator) {
const result = [];
let it = await iterator.next();
while(!it.done) {
result.push(it.value);
it = await iterator.next();
}
// 等同于下面的写法
/* for await (let it of iterator) {
result.push(it);
} */
return result;
}
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!