js 异步浅析
- 缘起:
js 异步编程一直广泛应用在业务中,常见的异步编程示例是: 回调函数,事件监听,Promise , Generator , promise , async/await 等; 所以有必要自己整理一下这一块的知识, 作为面试中的重灾区,有必要在这里进行总结;
- js 异步编程:
最开始的时候,通过回调函数的方式来实现异步编程,像setTimeout , setInterval ,但是业务复杂的情况下,会陷入异步回调地狱的问题,为了解决这个问题,promise出现了, 举例说:
function returnPromise (arr) {
return new promise(resolve , reject => {
if(arr.length > 0){
resolve();
}else {
reject();
}
});
}
const myanswer = returnPromise(arr);
myanswer.then(res => {
}).then((res) => {
}).catch(err => {
});
上述是使用promise 的代码方式,但是呢, 遇到多个promise的情况下 , 这一种写的方式也是不优雅的; Generator 方式应运而生, Generator 是异步任务的一种容器, Generator 配合 yield 进行实现;示例代码如下:
function* testGenerator(){
let a = yield 111;
console.log(a);
let b = yield 222;
console.log(b);
let c = yield 333;
console.log(c);
}
let t = testGenerator();
t.next(1); // 输出1;
t.next(2); // 输出2;
t.next(3); // 输出3;
上述是Generator 函数的使用方式; 之后又发展出了async / await 的方式 ; async / await 的使用方式使得异步编程的代码可以写成同步的方式:
代码示例:
function returnPromise (arr) {
return new promise(resolve , reject => {
if(arr.length > 0){
resolve();
}else {
reject();
}
});
}
async usingreturnPromise = (arr) =>{
let result = await returnPromise(arr);
if(result) {
// 执行true 的逻辑;
}else {
// 执行false的逻辑;
}
}
上面是使用async \ await 方式实现的形式,这里需要说明的一点是await 可以返回一个promise 对象,也可以返回一个值对象,所以,async 里面使用await 修饰一个普通的函数也是可以的;
- pomise 的原生实现:
手动实现一个promise , 首先要知道他的状态 , promise 的三种状态是fullfilled , pending , rejected ; resolve 和 reject 同时返回一个promise 对象,promise 通过prototype 的方式可以继续调用,在写promise的原生实现之前 ,首先是要先熟悉为了解决异步回调地狱的问题 , promise 的设计思路:
promise 使用回调函数延迟绑定、 返回值穿透、错误冒泡的方式进行实现; 除了这三个基本的设计思路,promise 还有四个基本的api 可供调用:
- all
- allSettled
- race
- any
首先说明all 方法:
使用promise.all的时候,resolve 的情况下,promise 会按照顺序返回promise 的执行情况,如果遇到rejected 的情况,promise.all会返回第一个出现reject 的情况;
allSettled 不会返回失败的情况,只会返回每个promise 执行的情况;
const resolved = Promise.resolve(1);
const rejected = Promise.reject(-1);
Promise.all([resolved , rejected]).then (res => {
console.log(res);
});
/**
__proto__: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: -1
*/
上面是allSettled 的执行结果;
Promise.race 首先是返回第一个状态生成的Promise对象,图片加载这一个业务可以使用Promise.race 进行实现, race首先是返回promise 迭代对象中有状态返回的那个promise (说的有点low , 懂得都懂);
const getImage = () => {
return new Promise((resolve,reject) => {
fetch(url, {
name:'testPromise'
}).then((res) => {
resolve(res);
});
});
}
const timeout = () => {
return new Promise(resolve , reject => {
setTimeout(() => console.log('timeout '),1000);
});
}
const raceResult = Promise.race([getImage , timeout]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
如果遇到timeout 首先执行的情况,说明获取图片是失败的; 使用race 是可以实现这一个业务需求的;
Promise.any 方式是参数只要有一个成功 , 就返回那个成功的结果,所有都失败的话 , promise 实例变成rejected 的状态;
上述是在手动实现Promise 之前需要的基础知识;
- Generator:
接着介绍一下Generator 的基本使用,Generator是一个函数的容器; 上代码:
function* gen1() {
yield 1;
yield* gen2();
yield 4;
}
function* gen2() {
yield 2;
yield 3;
}
var g = gen1();
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())
// output:
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// {value: undefined, done: true}
yield 经常配合Generator 实现控制函数的执行、暂停功能;
其实, Generator 的功能远远不止着一些 , 使用Generator 可以和thunk 结合 , 使用thunk 功能减少代码的复用:
thunk 集合Generator 的实例是:
// thunk 写法; 类似于函数柯里化的概念;
const readFileThunk = (filename) => {
return (callback) => {
fn.readFile(filename,callback);
}
}
function run(gen){
const next = (err , data) => {
let res = gen.next();
if(res.done) return;
res.value(next);
}
next();//继续执行该函数;
}
除了Generator 和 thunk 结合之外 , Generator 还可以和promise 结合使用;
上代码:
// 最后包装成 Promise 对象进行返回
const readFilePromise = (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(filename, (err, data) => {
if(err) {
reject(err);
}else {
resolve(data);
}
});
}).then(res => res);
}
// 这块和上面 thunk 的方式一样
const gen = function* () {
const data1 = yield readFilePromise('1.txt')
console.log(data1.toString())
const data2 = yield readFilePromise('2.txt')
console.log(data2.toString)
}
// 这块和上面 thunk 的方式一样
function run(gen){
const next = (err, data) => {
let res = gen.next(data);
if(res.done) return;
res.value.then(next);
}
next();
}
run(g);
这是Promise 和 thunk 的结合方式; run 函数的写法就是thunk 的实现形式;
另外 , 关于一点还需要说一下 , co函数库结合Generator 也是异步的解决方案之一; co函数库的源码也是值得学习的,学习一下co函数库作者TJ的编程方式;
about me: web 前端开发一年半 , 金三银四最近在看机会 , 欢迎交流; wechat number : Yingbin192;