本文将从js的异步历史介绍开始,到亲自手写一个async函数的模拟实现~node
正文开始~git
咱们都知道JavaScript是单线程的,避开了操做多线程的上锁和复杂的状态同步问题,单线程是没法充分利用cpu的,不过早期的JavaScript只是做为浏览器的脚本工具实现的,所以采用单线程是最方便最省事的,做为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操做DOM。它只能是单线程,不然会带来很复杂的同步问题。好比,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准?es6
受制于单线程的缘由,JavaScript在处理异步任务的时候就出现了比较尴尬的局面,好比AJAX请求,若是事情只能一件一件的来,当用户到服务器上去请求的时候,必须干等到结果回来才能继续后面的动做,这显而易见是不能被接受的,所以callback顺势而出,到后来的nodejs崛起,实现IO读取等异步的方式都是采用了callback的方式,也诞生了臭名昭著的callback hell,场面开始失控...github
function (param, cb){
false.readFile(param, function(err, data){
if(err) return cb(err);
async1(data, function(err, data){
if(err) return cb(err);
async2(data, function(err, data){
if(err) return cb(err);
// asyncN... 不知道会有多少
})
})
})
}
复制代码
个人意中人是盖世英雄,有一天他会踏着七色云彩前来拯救我ajax
es6将promise归入了JavaScript的标准,promise表明着“承诺”,初始化了一个“承诺”以后,你只要在then中定义好这个承诺如果在将来达成要作什么事情,在catch中定义好这个承诺失败了须要作什么事情,就ok了promise
const somePromiseObject = return new Promise((resolve, reject) => {
const result = doSomeThing();
if(result) {
return resolve(result); // handle success
}
reject('err') // handle error
})
somePromiseObject
.then(data => {
// success callback
},
err => {
// err callback
})
复制代码
promise的出现完美的解救了callback的回调地狱,缘由是promise容许链式调用,而且能够统一到最外层去作错误的catch~浏览器
somePromiseObject
.then(data => {
// do someThings..
})
.then(data => {
// do someThings..
})
.then(data => {
// do someThings..
})
.catch(err => {
// handle errs here
})
复制代码
but 当咱们认为promise就是JavaScript解决异步的最优方案的时候,ES2017 标准又引入了async 函数,使得异步操做变得更加方便bash
我猜中了前头可我猜不中这结局 --promise服务器
没错就在es6将promise加入标准后的不久,es7又新加入一种新的异步解决方案(号称终极解决方案)-- async函数多线程
假设有个异步函数
ajax1().then(() => {
ajax2().then(() => {
ajax3().then((data) => {
console.log(data) // success
}, err => {
console.log(err)
})
},
err => {
console.log(err)
})
}, err => {
console.log(err)
})
复制代码
尽管promise能链式的处理下去,可是嵌套过多的话,维护起来仍是比较头疼的,并且错误的处理也至关的麻烦,须要每一个可能的错误都去关注,因此,咱们赶忙试试用async来实现
const asyncFun = async () => {
try{
await ajax1();
await ajax2();
const data = await ajax3();
console.log(data) // success
}catch(err){
console.log(err) // handle errs here
}
}
复制代码
能够看到明显的看到,async的写法比promise的链式更舒服,它容许咱们像写同步的语法同样去写嵌套的异步,实际的效果也会按照async 被 await 的顺序来运行,并且对开发最友好的是全部中间出现的错误都能被最外层的catch给捕获,因此说不少人都认为async将是js异步的终极解决方案,下面咱们先来认识一下实现async的函数的一个基础函数,generator函数~
function* gen(x) {
const y = yield x + 2;
const y1 = yield y + 3;
return y1;
}
var g = gen(1);
console.log(g.next()); // { value: 3, done: false }
console.log(g.next(2)); // { value: 5, done: false }
console.log(g.next()); // { value: undefined, done: true }
复制代码
既然generator能够暂停,若是咱们每次遇到promise就暂停,等拿到promise.then()执行的结果在返回data,岂不是就是async函数的表现形式?dei,这确实就是async实现的基本原理,下面咱们一块儿来手撸一个模拟async的函数--"myAsync",盘它!
// 函数A(正常状况);
const test = async function myGenerator(){
const data = await Promise.resolve("success");
console.log(data);
}
// 函数B(模拟状况);
function myAsync(myGenerator) {
// handle...
}
function* myGenerator() {
const data = yield Promise.resolve("success");
console.log(data); // success
}
const test = myAsync(myGenerator);
复制代码
// 函数B(模拟状况);
function myAsync(myGenerator) {
const gen = myGenerator(); // 生成迭代器
const handle = genResult => {
if (genResult.done) return; // 若是迭代器结束了,直接返回;
return genResult.value instanceof Promise // 判断当前迭代器的value是不是Promise的实例
? genResult.value
// 若是是,则等待异步完成后继续递归下一个迭代,并把resolve后的data带过去
.then(data => handle(gen.next(data)))
.catch(err => gen.throw(err)) // gen.throw 能够抛出一个容许外层去catch的err
: handle(gen.next(genResult.value)); // 若是不是promise,就能够直接递归下一次迭代了
};
try {
handle(gen.next()); // 开始处理next迭代
} catch (err) {
throw err;
}
}
function* myGenerator() {
const data = yield Promise.resolve("success");
console.log(data); // success
}
const test = myAsync(myGenerator);
复制代码
到如今其实咱们的核心功能handle函数就完成了,如今去调用next()方法,已经看到能够打印success了,可是,除了正常的promise,咱们还要考虑下面的状况
// 函数A(正常状况);
const a = {
then: () => {
console.log("then");
return 123123;
}
};
const test0 = async function() {};
const test1 = async function() {
return 123;
};
const test2 = async function() {
console.log(123);
};
const test4 = async function() {
return a;
};
test0().then(console.log); // undefined
test1().then(console.log); // 123
test2().then(console.log); // 123 undefined
test4().then(console.log); // then
复制代码
也就是说在async接受到的函数不是generator函数的状况下:
因此如今的代码变成
// 函数B(模拟状况);
function myAsync(myGenerator) {
// 判断接受到的参数不是一个generator函数
if (
Object.prototype.toString.call(myGenerator) !==
"[object GeneratorFunction]"
) {
// 若是是一个普通函数
if (
Object.prototype.toString.call(myGenerator) === "[object Function]"
) {
return new Promise((resolve, reject) => {
// 默认返回一个promise对象
try {
const data = myGenerator();
return resolve(data); // 尝试运行这个函数,并把结果resolve出去
} catch (err) {
return reject(err); // 失败处理
}
});
}
// 若是参数含有then这个方法--thenable 鸭子类型
if (typeof myGenerator.then === "function") {
return new Promise((resolve, reject) => {
try {
// 运行这个对象的then函数,并resolve出去
const data = myGenerator.then();
return resolve(data);
} catch (err) {
return reject(err); // 失败处理
}
});
}
// 剩下的状况,统一resolve出去给的参数
return Promise.resolve(myGenerator);
}
const gen = myGenerator(); // 生成迭代器
const handle = genResult => {
if (genResult.done) return; // 若是迭代器结束了,直接返回;
return genResult.value instanceof Promise // 判断当前迭代器的value是不是Promise的实例
? genResult.value
.then(data => handle(gen.next(data))) // 若是是,则等待异步完成后继续递归下一个迭代,并把resolve后的data带过去
.catch(err => gen.throw(err)) // gen.throw 能够抛出一个容许外层去catch的err
: handle(gen.next(genResult.value)); // 若是不是promise,就能够直接递归下一次迭代了
};
try {
handle(gen.next()); // 开始处理next迭代
} catch (err) {
throw err;
}
}
const a = {
then: () => {
console.log("then");
return 123123;
}
};
const test0 = myAsync(function() {});
const test1 = myAsync(function() {
return 123;
});
const test2 = myAsync(function() {
console.log(123);
});
const test4 = myAsync(function() {
return a;
});
test0.then(console.log); // undefined
test1.then(console.log); // 123
test2.then(console.log); // 123 undefined
test4.then(console.log); // then
复制代码
有的同窗可能会说,咦,es7的 async 函数返回的那个是个函数,须要执行才能拿到结果,好比test0 应该是 test() 返回的是promise,可是模拟出来的怎么直接就拿到返回的promise了,其实很简单,你只要再用一个函数(好比myAsyncWrapper)把这个函数包装一下就ok~
myAsync(function*() {
try {
const data1 = yield new Promise(res => {
setTimeout(() => {
res(1234);
console.log("step 1"); // step 1
}, 1000);
});
console.log(data1); // step 1 打印1s后 打印 123
const data2 = yield new Promise(res => {
setTimeout(() => {
res(12342);
console.log("step 2"); // step 2
}, 1000);
});
console.log(data2); // step 2 打印1s后打印 12342
} catch (err) {
console.log(888, err);
}
});
复制代码
完美啊有么有,再来试试reject
myAsync(function*() {
try {
yield Promise.reject(123);
const data1 = yield new Promise(res => {
setTimeout(() => {
res(1234);
console.log("step 1");
}, 1000);
});
console.log(data1);
const data2 = yield new Promise(res => {
setTimeout(() => {
res(12342);
console.log("step 2");
}, 1000);
});
console.log(data2);
} catch (err) {
console.log(888, err); // 888 123
}
});
复制代码
没问题直接输出了888 123