深入 JavaScript 异步历程及本质
异步编程的解决方案和问题
采用单线程(JS执行环境中负责执行代码的线程只有一个)工作的原因
- 假设有多个线程同时工作
- 一个线程修改DOM,一个线程删除DOM
- 浏览器则无法明确该以哪个线程为准
同步和异步
- 同步:执行某个任务时,没有得到结果之前,不会继续后续的操作
- 异步:一个异步任务执行后,没有得到结果之前,就可以继续执行后续操作。异步任务完成后,一般通过回调通知调用者,比如setTimeout、fetch/XMLHttpRequest请求等
以一个需求来梳理下异步处理的解决方案
下面的login后拿到token,然后通过getOrderId拿到orderId,第三步操作拿到orderDetails详情消息去展示
回调函数-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
延时函数
简版
/**
*
* @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);
})
手写测试Promise
xixixiaoyu/handwritten-promise: 手写promise并测试 (github.com)
使用 promises-aplus-tests 库测试并全部通过
async的本质和注意事项
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 对象定义或定制它们的迭代行为,例如在一个 for...of 结构中,哪些值可以被遍历到
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);
}
打印结果如下:
发号器
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 的封装,一种新的语法糖
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等
基于事件通讯的场景:客户端相互之间
- webview与原生,页面与iframe,EventEmitter订阅发布中心等等
技术角度的两种场景
- 一发一收:类似我们http请求,一次发送只期待一次返回结果
- 单纯的接受。就是单纯期望收到服务的消息
- 比如直播业务,土豪们送出礼物后,我们会在聊天室监听礼物消息,然后触发礼物特效
异步回调转Promise通用方案
- 支持EventEmitter、 MQTT、 socket.io、 iframe、 webview等等场景
- 超时处理
- 要兼容不是一发一收的通讯情况
- 要兼容服务端不能处理key的情况
通用设计
核心基础类BaseAsyncMessager
子类实现的方法
必须实现
- subscribe:订阅消息
- request:实际发送消息的操作
可以重写的方法
- getReqCategory:获取请求的类别
- getReqkey:获取请求的唯一key
- getResCategory:获取响应的类别
- getResKey:获取响应的key,用于和请求的key对比,来查找对应的回调函数,然后让Promise继续执行
具体源码请看github:xixixiaoyu/async-change-promise: 异步代码转换为promise形式 (github.com)
转载自:https://juejin.cn/post/7179255213600014394