深入 JavaScript 异步历程及本质

lxf2023-04-07 18:29:01

异步编程的解决方案和问题

采用单线程(JS执行环境中负责执行代码的线程只有一个)工作的原因

  • 假设有多个线程同时工作
  • 一个线程修改DOM,一个线程删除DOM
  • 浏览器则无法明确该以哪个线程为准

同步和异步

  • 同步:执行某个任务时,没有得到结果之前,不会继续后续的操作
  • 异步:一个异步任务执行后,没有得到结果之前,就可以继续执行后续操作。异步任务完成后,一般通过回调通知调用者,比如setTimeout、fetch/XMLHttpRequest请求等

以一个需求来梳理下异步处理的解决方案

下面的login后拿到token,然后通过getOrderId拿到orderId,第三步操作拿到orderDetails详情消息去展示

深入 JavaScript 异步历程及本质

回调函数-callBack

  • 由调用者定义,交给执行者执行的函数
  • 可以理解为一件你想要做的事情
function login(callback) {
	setTimeout(() => {
		callback("token");
	}, 3000);
}

function getOrderId(token, callback) {
	if (token) {
		setTimeout(() => {
			callback("orderId");
		}, 2000);
	}
}

function orderDetails(orderId, callback) {
	if (orderId) {
		setTimeout(() => {
			callback("京东订单:购买xxx书一本");
		}, 1500);
	}
}

login((token) => {
	getOrderId(token, (orderId) => {
		orderDetails(orderId, (orderInfo) => {
			console.log(orderInfo);
		});
	});
});

缺点

  • 回调地狱
  • 高度耦合
  • 不易维护
  • 不能直接return

事件驱动

index.html

<script src="./login.js"></script>
<script src="./getOrderId.js"></script>
<script src="./orderDetails.js"></script>
<script>
  window.addEventListener("orderDetails-over", (e) => {
    console.log(e.detail);
  });
  login();
</script>

login.js

let loginEvent = new CustomEvent("login-over", {
	detail: "token",
});

function login() {
	setTimeout(() => {
		window.dispatchEvent(loginEvent);
	}, 3000);
}

getOrderId.js

const orderDetailsEvent = new CustomEvent("orderDetails-over", {
	detail: "京东订单:购买xxx书一本",
});

function orderDetails(orderId) {
	if (orderId) {
		setTimeout(() => {
			window.dispatchEvent(orderDetailsEvent);
		}, 1500);
	}
}

function orderIdListener(ev) {
	orderDetails(ev.detail);
}
//在window 上添加监听事件
window.addEventListener("orderId-over", orderIdListener);

orderDetails.js

const orderIdEvent = new CustomEvent("orderId-over", {
	detail: "orderId",
});

function getOrderId(token) {
	if (token) {
		setTimeout(() => {
			window.dispatchEvent(orderIdEvent);
		}, 2000);
	}
}

function tokenListener(ev) {
	getOrderId(ev.detail);
}
//在window 上添加监听事件
window.addEventListener("login-over", tokenListener);

优点

  • 去耦合
  • 便于实现模块化

缺点

  • 运行流程不清晰
  • 阅读代码困难

发布订阅

  • 也是使用事件驱动,但是有一个消息中心,可以查看消息流转
/**
 * 消息中心
 * @class MsgCenter
 */
class MsgCenter {
  constructor() {
    this.listeners = {};
  }

  // 订阅
  subscribe(type, listener) {
    if (this.listeners[type] === undefined) {
      this.listeners[type] = [];
    }
    this.listeners[type].push(listener);
    console.log(`${type}消息订阅数:${this.listeners[type].length}`);
    return listener;
  }

  // 发送
  dispatch(type, args = {}) {
    if (!type) {
      throw new Error("Event object missing 'type' property.");
    }
    if (Array.isArray(this.listeners[type])) {
      const listeners = this.listeners[type];
      for (let j = 0; j < listeners.length; j++) {
        listeners[j].call(this, type, args);
      }
    }
  }

  // 取消订阅
  unSubscribe(type, listener) {
    if (Array.isArray(this.listeners[type])) {
      const listeners = this.listeners[type];
      for (let i = 0; i < listeners.length; i++) {
        if (listeners[i] === listener) {
          listeners.splice(i, 1);
          break;
        }
      }
      console.log(`${type}消息订阅数:${this.listeners[type].length}`);
    }
  }

  // 获取某种消息所有订阅
  getTypeSubscribe(type) {
    return this.listeners[type] || [];
  }

  // 销毁
  destroy() {
    this.listeners = null;
  }
}

const MyMsgCenter = new MsgCenter();

export default MyMsgCenter;

login.js

import MyMsgCenter from "./MsgCenter.js";

export function login() {
    setTimeout(() => {
        MyMsgCenter.dispatch("login-over",{ detail:"token" });
    }, 3000);
}

getOrderId

import MyMsgCenter from "./MsgCenter.js";

function getOrderId(token) {
	if (token) {
		setTimeout(() => {
			MyMsgCenter.dispatch("orderId-over", { detail: "orderId" });
		}, 2000);
	}
}

function tokenListener(type, ev) {
	getOrderId(ev.detail);
	MyMsgCenter.unSubscribe(type, tokenListener);
}

MyMsgCenter.subscribe("login-over", tokenListener);

orderDetail.js

import MyMsgCenter from "./MsgCenter.js";

function orderDetails(orderId) {
	if (orderId) {
		setTimeout(() => {
			MyMsgCenter.dispatch("orderDetails-over", {
				detail: "京东订单:购买xxx书一本",
			});
		}, 1500);
	}
}

function orderIdListener(type, ev) {
	orderDetails(ev.detail);
	MyMsgCenter.unSubscribe(type, orderIdListener);
}

MyMsgCenter.subscribe("orderId-over", orderIdListener);

start.js

import { login } from "./login.js";
import MyMsgCenter from "./MsgCenter.js";

function resultListener(type, ev) {
	console.log("收到结果:", ev.detail);
	MyMsgCenter.unSubscribe(type, resultListener);
}

MyMsgCenter.subscribe("orderDetails-over", resultListener);

window.login = login;

index.html

发布订阅模式
<script src="./start.js" type="module"></script>
<script src="./getOrderId.js" type="module"></script>
<script src="./orderDetails.js" type="module"></script>
<script>
  window.onload = function () {
    if (window.login) {
      window.login();
    }
  };
</script>

Promise

  • 拉平回调函数,把回调嵌套变为链式调用
function login() {
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve("token")
        }, 3000);
    })
}


function getOrderId(token) {
    return new Promise((resolve,reject)=>{
        if(token){
            setTimeout(() => {
                resolve("orderId");
            }, 2000);
        }
    })    
}

function orderDetails(orderId) {
    return new Promise((resolve,reject)=>{
        if(orderId){
            setTimeout(() => {
                resolve("京东订单:购买xxx书一本");
            }, 1500);
        }
    })
}


login().then(getOrderId).then(orderDetails).then((result)=>{
    console.log(result);
});


// 还可以这样写
// const resultPromise = login().then(getOrderId).then(orderDetails);

// resultPromise.then((result)=>{
//     console.log(result);
// })

优点

  • 链式调用,流程清晰

缺点

  • 代码冗余,不够简洁
  • 无法取消Promise
  • 错误需要通过回调函数捕获

Generator

  • Generator 最大特点就是可以控制函数执行
function login() {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve("token");
		}, 3000);
	});
}

function getOrderId(token) {
	return new Promise((resolve, reject) => {
		if (token) {
			setTimeout(() => {
				resolve("orderId");
			}, 2000);
		}
	});
}

function orderDetails(orderId) {
	return new Promise((resolve, reject) => {
		if (orderId) {
			setTimeout(() => {
				resolve("京东订单:购买xxx书一本");
			}, 1500);
		}
	});
}

function* execute() {
	const token = yield login();
	const orderId = yield getOrderId(token);
	const orderInfo = yield orderDetails(orderId);
}

let g = execute();

let { value, done } = g.next();

value.then((token) => {
	console.log("token==", token);
	let { value, done } = g.next(token);
	value.then((orderId) => {
		console.log("orderId==", orderId);
		let { value, done } = g.next(orderId);
		value.then((orderInfo) => {
			console.log("orderInfo==", orderInfo);
		});
	});
});

优点

  • 可以控制函数执行

缺点

  • 执行时机太过麻烦

异步终极方案-async+await

  • async 函数就是 Generator 函数的语法
function login() {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve("token");
		}, 3000);
	});
}

function getOrderId(token) {
	return new Promise((resolve, reject) => {
		if (token) {
			setTimeout(() => {
				resolve("orderId");
			}, 2000);
		}
	});
}

function orderDetails(orderId) {
	return new Promise((resolve, reject) => {
		if (orderId) {
			setTimeout(() => {
				resolve("京东订单:购买xxx书一本");
			}, 1500);
		}
	});
}

async function execute() {
	const token = await login();
	const orderId = await getOrderId(token);
	const orderInfo = await orderDetails(orderId);
	console.log(orderInfo);
}

execute();

优点

  • 内置执行器
  • 语义更好

缺点

  • 函数声明 async 蝴蝶效应(遍地都是await)

异步方案总结

  • 异步编程进化史:callback => promise => generator => Async+await
  • Promise最大贡献就是统一了JavaScript 异步编程标准(Generator、Async/await)
  • async+await为目前异步通信终极方案

现代异步编程核心Promise

Promise的三种状态

  • pending(待定)- 初始状态,既没有兑现,也没有被拒绝
  • fulfilled(已兑现)- 意味着操作成功
  • rejected(已拒绝)- 意味着操作失败

状态只能由 pending => fulfilled 或者 pending => rejected

Promise.prototype.then

  • Promise 对象的 then 方法会返回一个全新的 Promise 对象
  • 后面的 then 方法就是在为上一个 then 返回的 Promise 注册回调
  • 前面 then 方法中回调函数的返回值会作为后面 then 方法回调的参数
  • 如果回调中返回的是 Promise,那后面 then 方法的回调会等待它的结果

Promise API

深入 JavaScript 异步历程及本质

深入 JavaScript 异步历程及本质

延时函数

简版

/**
 *
 * @param {any} fn 需要延迟的方法
 * @param {any} delay 延迟时间
 * @param {any} context 上下文
 * @returns
 */
function delay(fn, delay, context) {
	let defaultDelay = delay || 5000;
	let ticket;
	return {
		run(...args) {
			ticket = setTimeout(async () => {
				fn.apply(context, args);
			}, defaultDelay);
		},
		cancel: () => {
			clearTimeout(ticket);
		},
	};
}

const { run, cancel } = delay(() => {
	console.log("111");
}, 3000);

run();

setTimeout(() => {
	cancel();
}, 1000);

Promise封装

function isFunction(fn) {
	return typeof fn === "function" || fn instanceof Function;
}

/**
 *
 * @param {any} fn 需要延迟的方法
 * @param {any} delay 延迟时间
 * @param {any} context 上下文
 * @returns
 */
function delay(fn, delay, context) {
	let defaultDelay = delay || 5000;
	if (!isFunction(fn)) {
		return {
			run: () => Promise.resolve(),
			cancel: noop,
		};
	}
	let ticket;
	let executed = false;
	return {
		run(...args) {
			return new Promise((resolve, reject) => {
				if (executed === true) {
					return;
				}
				executed = true;
				ticket = setTimeout(async () => {
					try {
						const res = await fn.apply(context, args);
						resolve(res);
					} catch (err) {
						reject(err);
					} finally {
						clearTimeout(ticket);
					}
				}, defaultDelay);
			});
		},
		cancel: () => {
			clearTimeout(ticket);
		},
	};
}

//测试
const { run, cancel } = delay(() => {
	return "函数执行结果";
}, 3000);

run().then((result) => {
	console.log("result:", result);
});

run().then(() => {
	console.log("多次调用run result:", result);
});

支持失败重复多次

function isFunction(fn) {
    return typeof fn === 'function' || fn instanceof Function
}

function retry(fun, count, assert = () => false) {
    if (!isFunction(fun)) {
        return Promise.resolve();
    }
    return new Promise(async (resolve, reject) => {
        let error = null; //错误值
        for (let tryCount = 1; tryCount <= count; tryCount++) {
            try {
                const value = await fun(tryCount);
                if (assert(value, tryCount)) {
                    return resolve(value);
                }
            } catch (e) {
                error = e;
            }
        }
        reject(new Error("多次尝试失败"))
    });

}


// retry(()=>{
//     throw new Error("错误")
// },3).catch((e)=>{
//     console.log("捕获到错误:",e)
// });

let index = 0;

function createPromise(tryCount) {
    console.log("尝试次数:", tryCount)
    return new Promise((resolve, reject) => {
        index++;
        setTimeout(() => { resolve(index) }, 1000)
    })
}

retry(createPromise, 10, (res) => {
    return res == 5
}).then((res) => {
    console.log("当前的数据:", res);
}).catch((e) => {
    console.log("捕获到错误:", e)
});

promise-fun:基于Promise封装了很多花样的工具库

注意事项

统一catch错误

  • Promise链推荐末尾添加.catch捕获错误
  • 如果没有加,可监听unhandledrejection(不太推荐将错误留给全局捕获处理)
window.addEventListener("unhandledrejection", (event) => {
  console.error(`捕获到错误1: ${event.reason}`);
});

window.onunhandledrejection = (event) => {
  console.error(`捕获到错误2: ${event.reason}`);
};

// 不能捕获
// window.onerror= (event) => {
//     console.error(`onerror 捕获到错误: ${event.reason}`)
//};

const p1 = new Promise((resolve, reject) => {
  throw new Error("错误");
  resolve(5);
});

执行顺序

  • new Promise里面函数立即执行
  • 微任务和宏任务都算异步任务,微任务先执行
console.log("1");

const p1 = new Promise((resolve) => {
	console.log("2");
	// 请注意
	resolve("resolve");
	//不会终止,会继续执行后面的代码,推荐上面写成return resolve("resolve");
	console.log("继续执行");
});

// 微任务
p1.then((result) => {
	console.log("p1 result");
});

// 宏任务
setTimeout(() => {
	console.log("3");
});

console.log("4");

Promise resolve以后,再报错无效

const p2 = new Promise((resolve, reject) => {
    resolve(5);
    throw new Error("自定义错误");
});

p2.then((res)=>{
    //不会执行
    console.log("res1",res);
    return res + 1;
}).then((res)=>{
    //不会执行
    console.log("res2",res)
}).catch((e)=>{
    console.log("reject 错误:",e);
});

then 传入非函数,值穿透

const p2 = new Promise((resolve, reject) => {
    resolve(6);
});

p2.then(1).then((res)=>{
    console.log("res2",res) // 6
    return 2;
}).catch((e)=>{
    console.log("reject 错误:",e);
});

all,race等reject以后,其他依旧执行

let p1 = new Promise((resolve) => {
    setTimeout(() => {
        console.log('执行p1');
        resolve('https://aaa.flv 开始播放')
    }, 5000)
})
let p2 = new Promise((resolve) => {
    setTimeout(() => {
        console.log('执行p2');
        resolve('https://bbb.flv 开始播放')
    }, 2000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('执行p3');
        reject('https://ccc.flv 播放失败')
    }, 1000)
})

Promise.race([p1, p2, p3]).then((res) => {
    console.log("已经获取到合适的结果了===", res);
})

深入 JavaScript 异步历程及本质

手写测试Promise

xixixiaoyu/handwritten-promise: 手写promise并测试 (github.com)

使用 promises-aplus-tests 库测试并全部通过

深入 JavaScript 异步历程及本质

async的本质和注意事项

Generator 函数(生成器函数)

  • redux-saga等库有其应用
  • 关于redux-saga可看我这篇文章 深入React + Redux及其常用saga等中间件的使用 - AdminJS (Admin.net)
  • Generator函数就是一个普通函数
function* generator() {} instanceof Function  // true

函数特征

  • 星号:function关键字与函数名之间有一个星号
function* generator1(){ /*code*/ }// 推荐
function *generator2(){ /*code*/ }
function * generator3(){ /*code*/ }
function*generator4(){/*code*/ }
  • 生成器函数会返回一个生成器对象,调用这个生成器对象的 next 方法才会让函数体执行
  • 一旦遇到了 yield 关键词,函数的执行则会暂停下来,next 函数的参数作为 yield 结果返回
  • 如果继续调用函数的 next 函数,则会在上一次暂停的位置继续执行
  • 反复反复,直到遇到 return 或者没有语句,next 返回的对象的 done 就变成了 true
function* numGenerator() {
	yield 1;
	yield 2;
	return 3;
}

const gen = numGenerator();
console.log(gen.next()); // { value: 1,  done: false }
console.log(gen.next()); // { value: 2,  done: false }
console.log(gen.next()); // { value: 3, done: true }
console.log(gen.next()); // { value: undefined, done: true }

判断是不是Generator函数

var GeneratorFunction = Object.getPrototypeOf(function* () {}).constructor;
function* numGenerator() {
	yield 1;
	yield 2;
	return 3;
}

console.log(numGenerator.constructor === GeneratorFunction); // true

Generator 对象

  • Generator函数执行会返回的对象,叫做Generator对象

方法

  • next:获取下一个状态
  • return:给定的值并结束生成器
  • throw:向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象
function* numGenerator() {
	yield 1;
	yield 2;
	yield 3;
}

const gen = numGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return(10)); // { value: 1, done: false }
console.log(gen.next()); // { value: undefined, done: true }

Generator捕获异常

function* generator() {
	var val = 1;
	while (true) {
    // 不使用try...catch会导致程序异常无法继续执行
		try {
			yield val++;
		} catch (e) {
			console.log("Error caught!");
		}
	}
}

const gen = generator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw(new Error("Something went wrong"))); // "Error caught!"  {value: 2, done: false}
console.log(gen.next()); // { value: 3, done: false }

捕获异常之后依然正常执行且有返回值,结果如下:

深入 JavaScript 异步历程及本质

迭代器协议

  • 定义了产生一系列值(无论是有限个还是无限个)的标准方式
  • 当值为有限个时,所有的值都被迭代完毕后,则会返回默认返回值

深入 JavaScript 异步历程及本质

可迭代协议

  • 允许 JavaScript 对象定义或定制它们的迭代行为,例如在一个 for...of 结构中,哪些值可以被遍历到

深入 JavaScript 异步历程及本质

Generator对象符合可迭代协议和迭代器协议

const gen = (function* () {
	yield 1;
	yield 2;
	yield 3;
})();

// 返回”function”,因为有一个next方法,所以这是一个迭代器,可以用next不停的调用
typeof gen.next;

// 返回"function”,因为有一个@@iterator方法,所以这是一个可迭代对象 
// 所以可以使用 for...of,拓展运算符等操作
typeof gen[Symbol.iterator];

// 迭代器
gen.next(); // { value: 1, done: false }

//可迭代对象
for (let v of gen) {
	console.log(v); // 1 2 3
}

Generator函数的参数传递

外界向生成器函数传递参数

  • 生成器函数本身可以接受初始化参数
  • Generatorprototype.next 可以向生成器函数传递参数
  • Generator.prototype.return 可以向生成器函数传递参数
  • Generator.prototype.throw 可以向生成器函数抛出异常

生成器函数向外输出结果

  • Generator.prototype.next 的返回值
  • generator.next(value)中,next传入的参数会作为上一次yield的返回值
function* addGenerator(num1) {
	let num2 = 0;
	while (true) {
		num2 = yield (num1 = num1 + num2);
	}
}
const gen = addGenerator(10);
// num1 = 10, num2 = 0 (第一次不能传参)
console.log(gen.next()); // 10
// num2 = 20, num1 = 10
console.log(gen.next(20)); // 30
// num2 = 30, num1 = 30
console.log(gen.next(30)); // 60

Generator实现异步

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.open("GET", url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if(this.status === 200)
        resolve(this.response)
      else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}
// 生成器函数
function * main () {
  const users = yield ajax('/api/users.json')
  console.log(users)
  
  const posts = yield ajax('/api/posts.json')
  console.log(posts)

  const urls = yield ajax('/api/urls.json')
  console.log(urls)
}
// 调用生成器函数得到一个生成器对象
const generator = main()

// 递归实现generator.next()的调用,直到done为true终止
// 注意:generator.next(value)中,next传入的参数会作为上一次yield的返回值
function dfs(value) {
  const result = generator.next(value)
  if(result.done) return
  result.value.then(data=>{
    console.log(data)
    dfs(data)
  })
}

dfs()
// 打印结果
// Generator实现异步.js:35 {username: "yibo"}
// Generator实现异步.js:19 {username: "yibo"}
// Generator实现异步.js:35 {posts: "jiailing"}
// Generator实现异步.js:22 {posts: "jiailing"}
// Generator实现异步.js:35 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator实现异步.js:25 {posts: "/api/posts.json", users: "/api/users.json"}

Generator妙用

序列生成器
function* sequenceGenerator(start = 0, step = 1, end = Number.MAX_SAFE_INTEGER) {
  let current = start;
  while (current <= end) {
    yield current;
    current = current + step;
  }
}

const gen1 = sequenceGenerator(0, 3, 6);
console.log(gen1.next());
console.log(gen1.next());
console.log(gen1.next());
console.log(gen1.next());

const gen2 = sequenceGenerator(0, 3, 6);
const numbers = [...gen2];
console.log(numbers);

const gen3 = sequenceGenerator(0, 3, 6);
for (let v of gen3) {
	console.log("v:", v);
}

打印结果如下:

深入 JavaScript 异步历程及本质

发号器
function * createIdMaker () {
  let id = 1
  while(true) {
    yield id++
  }
}
const idMaker = createIdMaker()

console.log(idMaker.next()) // { value: 1, done: false }
console.log(idMaker.next()) // { value: 2, done: false }
console.log(idMaker.next()) // { value: 3, done: false }
console.log(idMaker.next()) // { value: 4, done: false }
console.log(idMaker.next()) // { value: 5, done: false }
状态机
// PDCA 循环 Plan-Do-Check-Action
function* stateMachineGenerator(states, state) {
	const len = states.length;
	let index = Math.max(states.findIndex((s) => s === state), 0);
	while (true) {
		yield states[++index % len];
	}
}
const startState = "Do";
const stateMachine = stateMachineGenerator(["Plan", "Do", "Check", "Action"], startState);

console.log(startState); // Do
console.log(stateMachine.next().value); // Check
console.log(stateMachine.next().value); // Action
console.log(stateMachine.next().value); // Plan
console.log(stateMachine.next().value); // Do
console.log(stateMachine.next().value); // Check
console.log(stateMachine.next().value); // Action
console.log(stateMachine.next().value); // Plan
实现迭代器Iterator
const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '英语'],
  work: ['喝茶'],

  // 实现迭代器接口
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for (const item of all) {
      yield item
    }
  }
}

for(const item of todos) {
  console.log(item)
}
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 英语
// 喝茶

async函数

  • async函数是使用async关键字声明的函数
  • async函数是AsyncFunction构造函数的实例,并且其中允许使用 await 关键字
  • async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise
async function test() {
  // 如果await后没有跟promise,默认会将await后的值包裹一层Promise.resolve
	const r1 = await 1; 
	const r2 = await 2;
	console.log("result:", r1 + r2);
}

test(); // 3

async函数转译

上面的代码ES6编译后

"use strict";
var __awaiter = function (thisArg, _arguments, P, generator) {
    // 如果值是Promise, 直接返回,如果不是包裹成为Promise
    // 例如 await 1 => await Promise.resolve(1)
    function adopt(value) {
        return value instanceof Promise ? value :
            new Promise(function (resolve) { resolve(value); });
        // 可以使用 Promise.resolve(value)代替,Promise.resolve(value)
    }

    // 创建Promise
    return new Promise(function (resolve, reject) {
        // 成功执行
        function fulfilled(value) {
            try {
                // 取下一个
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            }
        }

        // 发生错误
        function rejected(value) {
            try {
                // 抛出异常
                step(generator["throw"](value));
            } catch (e) {
                reject(e);
            }
        }

        // 执行控制器
        function step(result) {
            // 执行完毕,直接调用resolve, 返回结果
            result.done ? resolve(result.value) :
                // 继续执行, 并传递上一次的执行结果
                adopt(result.value)
                    .then(fulfilled, rejected);
        }

        // 产生generator, bind定this
        generator = generator.apply(thisArg, _arguments || []);
        // 开始执行
        step(generator.next());
    });
};

function test() {
    return __awaiter(this, void 0, void 0, function* () {
        const r1 = yield 1;
        const r2 = yield 2;
        console.log("result:", r1 + r2);
    });
}
test();

async本质

  • 本质就是对 generator 的封装,一种新的语法糖

深入 JavaScript 异步历程及本质

co库

  • Generator 函数的自动执行
function isPromise(obj) {
	return "function" == typeof obj.then;
}

function toPromise(obj) {
	if (!obj) return obj;
	if (isPromise(obj)) return obj;

	// 简化
	return Promise.resolve(obj);

	// 额外处理了很多其他的数据类型
	// if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
	// if ('function' == typeof obj) return thunkToPromise.call(this, obj);
	// if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
	// if (isObject(obj)) return objectToPromise.call(this, obj);
	// return obj;
}

function co(gen) {
	var ctx = this;
	var args = Array.prototype.slice.call(arguments, 1);
	// we wrap everything in a promise to avoid promise chaining,
	// which leads to memory leak errors.
	// see https://github.com/tj/co/issues/180
	return new Promise(function (resolve, reject) {
		if (typeof gen === "function") gen = gen.apply(ctx, args);
		if (!gen || typeof gen.next !== "function") return resolve(gen);

		onFulfilled();
		function onFulfilled(res) {
			var ret;
			try {
				ret = gen.next(res);
			} catch (e) {
				return reject(e);
			}
			next(ret);
		}

		function onRejected(err) {
			var ret;
			try {
				ret = gen.throw(err);
			} catch (e) {
				return reject(e);
			}
			next(ret);
		}
		function next(ret) {
			// 检查是否结束
			if (ret.done) return resolve(ret.value);

			// 转为Promise
			var value = toPromise.call(ctx, ret.value);
			// 检查是不是Promise
			if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
			return onRejected(
				new TypeError(
					"You may only yield a function, promise, generator, array, or object, " +
						'but the following object was passed: "' +
						String(ret.value) +
						'"'
				)
			);
		}
	});
}

function getData(duration, data) {
	return new Promise((resolve, reject) => {
		setTimeout(function () {
			resolve(data * 2);
		}, duration);
	});
}

const gen = function* () {
	const start = Date.now();
	const num1 = yield getData(1000, 10);
	const num2 = yield getData(2000, 20);
	console.log("result:", num1 + num2, ", cost:", Date.now() - start);
};

co(gen);

简化一下

function ajax(url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.responseType = "json";
    xhr.onload = function () {
      if (this.status === 200) resolve(this.response);
      else {
        reject(new Error(this.statusText));
      }
    };
    xhr.send();
  });
}
// 生成器函数
function* main() {
  try {
    const users = yield ajax("/api/users.json");
    console.log(users);

    const posts = yield ajax("/api/posts.json");
    console.log(posts);

    const urls = yield ajax("/api/urls.json");
    console.log(urls);
  } catch (e) {
    // 如果生成器函数中,发生了异常,会被生成器对象的throw方法捕获
    console.log(e);
  }
}

// 封装了一个生成器函数执行器
function co(main) {
  // 调用生成器函数得到一个生成器对象
  const generator = main();

  // 递归实现generator.next()的调用,直到done为true终止
  function handleResult(result) {
    if (result.done) return;
    result.value.then(
      (data) => {
        handleResult(generator.next(data));
      },
      (error) => {
        generator.throw(error);
      }
    );
  }

  handleResult(generator.next());
}

co(main);

// Generator实现异步.js:42 {username: "yunmu"}
// Generator实现异步.js:20 {username: "yunmu"}
// Generator实现异步.js:42 {posts: "linduidui"}
// Generator实现异步.js:23 {posts: "linduidui"}
// Generator实现异步.js:42 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator实现异步.js:26 {posts: "/api/posts.json", users: "/api/users.json"}

注意事项

  • async函数里面,不是所有异步代码都需要await,主要是看业务的上是否真的有依赖关系

基于Promise的通用异步方案

基于事件通讯的场景:与服务端通讯

  • WebSocket, socketlO, mqttSSE等

深入 JavaScript 异步历程及本质

基于事件通讯的场景:客户端相互之间

  • webview与原生,页面与iframe,EventEmitter订阅发布中心等等

深入 JavaScript 异步历程及本质

技术角度的两种场景

  • 一发一收:类似我们http请求,一次发送只期待一次返回结果
  • 单纯的接受。就是单纯期望收到服务的消息
    • 比如直播业务,土豪们送出礼物后,我们会在聊天室监听礼物消息,然后触发礼物特效

异步回调转Promise通用方案

  • 支持EventEmitter、 MQTT、 socket.io、 iframe、 webview等等场景
  • 超时处理
  • 要兼容不是一发一收的通讯情况
  • 要兼容服务端不能处理key的情况

通用设计

深入 JavaScript 异步历程及本质

核心基础类BaseAsyncMessager

深入 JavaScript 异步历程及本质

子类实现的方法

必须实现

  • subscribe:订阅消息
  • request:实际发送消息的操作

可以重写的方法

  • getReqCategory:获取请求的类别
  • getReqkey:获取请求的唯一key
  • getResCategory:获取响应的类别
  • getResKey:获取响应的key,用于和请求的key对比,来查找对应的回调函数,然后让Promise继续执行

具体源码请看github:xixixiaoyu/async-change-promise: 异步代码转换为promise形式 (github.com)