JS异步编程
异步初识
setTimeout 与 setInterval
定时回调进行中断执行
let x = 3;
setTimeout(() => {
x = x + 3;
console.log(x);
}, 1000);
循环中断 每隔1000ms就会执行一次回调函数
setInterval(() => {
x = x + 3;
console.log(x);
}, 1000);
老的异步方案 利用setTimeout解决
function double(value, callback) {
setTimeout(() => callback(value * 2), 1000);
}
double(3, (x) => console.log(`I was given ${x}`));
console.log("我先输出");
异常处理
function double(value, success, failure) {
setTimeout(() => {
try {
if (typeof value !== 'number') {
throw 'value type is not number';
}
success(value * 2); //成功回调
} catch (e) {
failure(e); //错误回调
}
}, 1000);
}
double('1', (res) => {
console.log("res", res);
}, (e) => {
console.error(e);
});
回调地狱
嵌套异步回调 一个异步任务等待另一个异步任务的返回值,一个异步代码中执行另一部分异步代码时,可能会出现回调地狱,简单的说就是我们原来的异步解决方案就是通过回调函数进行异步操作,就会出现回调里面还有回调这样的情况,如果任务较多可能会出现回调嵌套很多次,也就是我们说的回调地狱。
function double(value, success, failure) {
setTimeout(() => {
try {
if (typeof value !== 'number') {
throw 'value type is not number';
}
success(value * 2); //成功回调
} catch (e) {
failure(e); //错误回调
}
}, 1000);
}
const successCallback = (x) => {
double(x, (y) => console.log("y", y));
};
const failureCallback = (e) => console.error('e', e);
double(2, successCallback, failureCallback);
Promise
初识Promise
Promise是一种有三种状态的对象
- 待定 pending
- 兑现 fulfilled 也称解决resolved
- 拒绝 rejected
//Promise
let p = new Promise(() => {});
console.log(p);
setTimeout(console.log, 0, p);
//Promise是一个有状态的对象
//#通过执行函数控制Promise状态
let p1 = new Promise((resolve, reject) => {
resolve(); //把状态切换为兑现
});
console.log(p1);
//Promise {<fulfilled>: undefined}
let p2 = new Promise((resolve, reject) => {
reject(); //把状态切换为拒绝
});
console.log(p2);
//Promise {<rejected>: undefined}
当p被输出时,Promise状态还为待定状态
因为还没有执行resolve或reject
Promise状态一旦改变则不可逆,继续修改状态会静默失败
Promise的状态只能改变一次
new Promise(() => setTimeout(console.log, 0, 'executor'));
setTimeout(console.log, 0, 'promise initialized');
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));
setTimeout(console.log, 0, p);
Promise.resolve
创建的Promise对象默认状态为待定
但Promise并非一开始必须为待定状态
可以通过Promise.resolve()静态方法实例化一个resolve状态的Promise
let p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
let p2 = Promise.resolve();
console.log("p1", p1); //Promise {<pending>}
console.log("p2", p2); //Promise {<fulfilled>: undefined}
//Promise.resolve是一个静态方法
Promise.resolve(any) 可以把任何参数变为一个Promise
console.log(Promise.resolve(2));
//Promise {<fulfilled>: 2}
如果参数是一个Promise 则返回原来的Promise
let p3 = Promise.resolve(3);
console.log(p3 === Promise.resolve(p3));
//true
能够包装任何非Promise值,包括Error Object,
可以将其转换为resolved的Promise
console.log(Promise.resolve(new Error('error to promise')));
/*
Promise {< fulfilled >: Error: error to promise}
*/
Promise.reject
`
实例化一个拒绝状态的Promise并抛出一个异步错误
`
let p1 = new Promise((resolve, reject) => {
reject();
});
let p2 = Promise.reject();
console.log("p1", p1);
console.log("p2", p2);
`
二者是等价的,都是实例化了一个rejected状态的Promise
且二者都抛出了Uncaught Error
`
let p3 = Promise.reject(3);
console.log(p3); //Promise {<rejected>: 3}
p3.catch((e) => {
console.log("e", e); //e
})
`
e 就是reject传入的参数
本质还是创建了一个Promise 调用了reject(3)
`
`
如果reject的参数是一个Promise 并不像resolve
它会将其变为一个reject的理由即变为catch的参数
`
let p4 = Promise.resolve('成功');
p4.then((res) => {
console.log(res);
return Promise.resolve('连起来');
}).then((res) => {
console.log(res);
return Promise.reject('结束吧');
}).then((res) => {
console.log(res);
}).catch((e) => {
console.log(e);
}).finally(() => {
console.log("一切都结束了");
});
通俗理解Promise
Promise是一种对象、其有三种状态,resolve 表示解决 其传参可以被then传入的回调函数接收、reject 表示拒绝 其传参可以被 catch传入的回调函数接收。
利用Promise可以解决一定的回调地狱问题、使得代码维护性更高。
有趣的例子
const isPregnant = true;
const promise = new Promise((resolve, reject) => {
if (isPregnant) {
resolve('孩子他爹');
} else {
reject('老公');
}
});
promise.then(name => {
//resolve
console.log('男人成为了' + name);
}).catch(name => {
//reject
console.log('男人成为了' + name);
}).finally(() => {
console.log('男人和女人最终结婚了');
});
一个实际的例子
const imgPromise = (url) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = url;
img.onload = () => {
resolve(img);
}
img.onerror = () => {
reject(new Error('图片加载出现错误'));
}
});
}
imgPromise('https:/cdn.jim-nielsen.com/ios/512/netflix-2018-11-01.png').then(img => {
document.body.appendChild(img);
}).catch(err => {
const p = document.createElement('p');
p.innerHTML = err;
document.body.appendChild(p);
});
//可见利用Promise可以有效的解决回调地狱问题
实现Thenable接口
class MyThenable {
then(res) {
res();
}
}
const able = new MyThenable();
able.then((res) => {
console.log("HELLO WORLD");
});
Promise.prototype.then()
两个参数 一个onResolved回调 一个 onRejected回调
第二个与使用.catch()效果一样
let p1 = new Promise((resolve, reject) => {
reject(new Error('none'));
});
let p2 = p1.then((res) => {
console.log(res);
}, (e) => {
console.log(e);
});
//.then()返回一个新的Promise p1!==p2
console.log(p2);
let p3 = Promise.resolve('resolve');
let p4 = p3.then(); //如果then没有参数则会保持p3的内容状态
console.log(p4); //但p4!==p3 但二者效果作用是一样的
p4.then((res) => {
console.log(res);
})
then里面的回调函数的返回值是then返回Promise所使用resolve
时的参数,所以上一个.then里的返回值在紧接着的.then里可以接收到
Promise.resolve(1).then((res) => {
console.log(res); //1
return 2;
}).then((res) => {
console.log(res); //2
throw Error('none');
}).catch((e) => {
console.log(e); //Error:none
});
Promise.prototype.catch()
只接受一个参数 onRejected处理程序
let p1 = new Promise((resolve, reject) => {
reject('拒绝');
});
//返回新的Promise
let p2 = p1.catch((e) => {
console.log(e);
});
console.log("p2", p2);
//可以继续使用p2 可以一致 then下去以及使用catch处理
let p4 = p2.then(() => {
console.log("p1.catch resolve");
});
//新的Promise .catch 与 .then 行为一样
let p3 = p1.catch();
p3.catch((e) => {
console.log("e", e);
});
Promise.prorotype.finally()
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('finally');
resolve('解决');
}, 0);
});
let p3 = p2.finally(() => {
console.log("onResolved 与 onRejected 都是执行");
console.log("但不知道是resolve还是reject");
});
console.log("p3", p3);
p3.then(() => {
console.log(".finally end");
});
非重入Promise方法
当Promise进行状态改变后、then catch 回调不一定立即执行
会将其放入执行队列 等待被执行
结果是Q F被输出执行
处理程序在同步代码没有执行完前不会被执行
then catch 中的处理程序并不会被同步执行
let p1 = Promise.resolve('res');
p1.then((res) => {
console.log(res);
});
console.log('Q');
console.log('F');
临近处理程序的执行顺序
let p1 = Promise.resolve();
p1.then((res) => {
console.log(1);
});
p1.then((res) => {
console.log(2);
});
p1.finally(() => {
console.log(3);
});
它们的执行顺序为代码顺序 1 2 3
reject 错误处理
//方法1
let p1 = new Promise((resolve, reject) => {
reject(Error('none'));
});
p1.catch((e) => {
console.log(e); //Error: none
});
//方法2
let p2 = new Promise((resolve, reject) => {
throw Error('throw error');
});
p2.catch((e) => {
console.log(e); //Error: throw error
});
这种方法异常处理是错误的,里面的异常并不被捕捉到
try {
p1 = new Promise((resolve, reject) => {
reject(Error('none'));
})
} catch (e) {
console.log(e);
}
Promise连锁
then catch finally 连起来使用
new Promise((resolve, reject) => {
resolve('Q');
}).then((res) => {
console.log(res);
}).catch((e) => {
console.log(e);
}).then((res) => {
console.log(res); //undefined
// 因为是catch返回的新的Promise
//在onResolved时并没有任何参数传入
});
如果有很多任务,进行同步,则可以实例化新的Promise
可以在处理程序中使用返回Promise的封装好的任务函数
可以有效的解决回调地狱问题
使用then catch 以及 finally 值观的进行编码
new Promise((resolve, reject) => {
resolve('1');
}).then((res) => new Promise((resolve, reject) => {
console.log(res);
resolve('2');
})).then((res) => {
console.log(res);
}).finally(() => {
console.log('finally');
});
Promise 图
进行了一个二叉树的层级遍历
then catch finally有可以注册多个处理程序的特性
let A = new Promise((resolve, reject) => {
console.log('A');
resolve();
});
let B = A.then(() => {
console.log("B");
});
let C = A.then(() => {
console.log("C");
});
B.then(() => {
console.log("D");
});
B.then(() => {
console.log("E");
});
C.then(() => {
console.log("F");
});
C.then(() => {
console.log("G");
});
Promise.all()
即所有Promise状态都改变后程序进行执行
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2');
}, 5000);
});
Promise.all([p1, p2]).then((ress) => {
console.log(ress); // ['p1', 'p2']
});
//会等到p1 p2 状态都改变后执行
Promise.race()
返回状态最先改变的Promise
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1');
resolve('p1');
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2');
resolve('p2');
}, 5000);
});
let p_race = Promise.race([p2, p1]);
p_race.then((res) => {
console.log(res); //p1
//因为p1的状态最先改变
});
串行Promise的合成
function add1(x) {
return x + 1;
}
function add2(x) {
return x + 2;
}
function add3(x) {
return x + 3;
}
console.log(add1(add2(add3(0)))); //6
function addAll(x) {
return Promise.resolve(x).then(add1).then(add2).then(add3);
}
addAll(0).then((res) => {
console.log(res); //6
});
可以使用Array.prototype.reduce简化代码
function AddAllPro(x) {
return [add1, add2, add3].reduce((promise, fn) => {
return promise.then(fn); //给下一个Promise
}, Promise.resolve(x));
//reduce第二个参数为进行reduce原始值
//比如求和 则设为0即可理解就好
}
AddAllPro(0).then((res) => {
console.log(res); //6
});
继续compose函数的封装
function compose(...fns) {
return (x) => fns.reduce((promise, fn) => {
return promise.then(fn);
}, Promise.resolve(x));
}
compose(add1, add2, add3)(0).then((res) => {
console.log(res); //6
});
Promise 进行取消事件取消
由Kevin Smith提到的"取消令牌" (cancel token)
class CancelToken {
constructor(cancelFn) {
this.promise = new Promise((resolve, reject) => {
console.log("任务");
cancelFn(resolve, reject); //暴露resolve,reject
});
}
}
let resolve, reject;
let t1 = new CancelToken((resolve_, reject_) => {
resolve = resolve_;
reject = reject_;
console.log(resolve);
console.log(reject);
});
let p = t1.promise;
let id = setTimeout(() => {
console.log("assign")
}, 1000);
p.finally(() => {
clearTimeout(id);
});
resolve();
//可以随时取消console.log("assign")程序任务
console.log(p);
实现Promis.notify 进度通知
class TrackablePromise extends Promise {
constructor(excutor) {
const notifyHandlers = []; //存储需要通知的函数
super((resolve, reject) => {
excutor(resolve, reject, (info) => {
notifyHandlers.forEach((handler) => {
handler(info);
})
})
});
this.notifyHandlers = notifyHandlers;
}
//添加事件
notify(notifyHandler) {
this.notifyHandlers.push(notifyHandler);
return this;
}
}
let p = new TrackablePromise((resolve, reject, notify) => {
function countdown(x) {
if (x > 0) {
notify(`${20 * x} % remaining`);
setTimeout(() => countdown(x - 1), 1000);
} else {
resolve();
}
}
countdown(5);
});
p.notify((x) => console.log(x));
p.then((res) => console.log("end", res));
/*
80 % remaining
60 % remaining
40 % remaining
20 % remaining
end undefined
*/
更新中 最后编辑(2022/2/21)…