为什么要用Promise (承诺) 多层回调代码降维 ,异步编程
来看一下以下代码:ajax的多次请求(形成V型代码结构),产生了回调地狱,
不论是浏览器中最为常见的ajax、事件监听,还是node中文件读取、网络编程、数据库等操作,都离不开异步编程。在异步编程中,许多操作都会放在回调函数(callback)中。同步与异步的混杂、过多的回调嵌套都会使得代码变得难以理解与维护,这也是常受人诟病的地方。————(某博客摘录)
代码降维思想 A执行成功后 把自己的状态交给S1, B监听S1的状态,成功后执行,代码结构就变得清晰
A
S1 pending => success/error
B
S2 pending => success/error
C
S3 pending => success/error
D
S4 pending => success/error
E
Promise 就此受到广泛的应用
古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。 ————(某大牛博客摘录)
Promise 是一种异步解决方案,最早由社区提出并实现,后来写进了es6规范
模拟ABC执行:
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`A is finshed`)
resolve('YES')
}, 2000)
})
let p2 = p1.then((data) => {
console.log(data)
return new Promise((suc, err) => {
setTimeout(() => {
err('NO')
console.log(`B is finshed`)
}, 2000)
})
}, (err) => {
console.log(err)
})
let p3 = p2.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
图片请求
function loadUrl(url) {
let img = new Image()
return new Promise((resolve, reject) => {
img.onload = () => resolve(img)
img.noerror = () => reject(`${url} is not an effective URL`)
img.src = url
})
}
//错误的URL 有BUG
loadUrl('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1536508763099&di=cf94cd77fc462f08cc9e47df4f48bef1&imgtype=jpg&src=http%3A%2F%2Fimg3.imgtn.bdimg.com%2Fit%2Fu%3D4010600603%2C2924105424%26fm%3D214%26gp%3D0.jpg').then(img => document.body.appendChild(img), err => console.log(err))
Promise对象有以下两个特点:对象状态不受外界影响 和 一旦状态改变,就不会再变,任何时候都可以得到这个结果
promise状态的不可逆性;
let p = new Promise((resolve, reject)=> {
reject("我执行了,后面改变不了状态")
resolve('他先执行他是老大')
})
Promise也有一些缺点:
1.一旦新建,就会立即执行,无法中途取消。
2.如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
3.当处于pending状态时,无法得知目前进展到哪一个阶段。
(如果某些事件不断地反复发生,一般来说,使用stream模式比部署Promise更好。)不了解stream模式 (摘录)
手动封装promise功能实现(同步版本。未封装异步状态)
class myPromise {
constructor(fn) {
if (typeof (fn) !== 'function') {
throw TypeError(`myPromise resolve ${fn} is not a function`)
}
this.status = 'pending'
this.data = undefined
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'
this.data = value
}
}
let reject = (value) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.data = value
}
}
if (this.status === 'pending') {
}
fn(resolve, reject)
}
then(resolveFn, rejectFn) {
if (this.status === 'resolved') {
if (typeof resolveFn !== 'function') {
return myPromise.resolve(this.data)
} else {
let res = resolveFn(this.data)
if (res instanceof myPromise) {
return res
} else {
return myPromise.resolve(this.data)
}
}
}
if (this.status === 'rejected') {
if (typeof rejectFn !== 'function') {
return myPromise.reject(this.data)
} else {
let res = rejectFn(this.data)
if (res instanceof myPromise) {
return res
} else {
return myPromise.resolve(this.data)
}
}
}
}
static resolve(data) {
return new myPromise((resolve, reject) => {
resolve(data)
})
}
static reject(data) {
return new myPromise((resolve, reject) => {
reject(data)
})
}
}
经过偶尔的灵感突发和XXX最终实现了异步,对于处于在pending的封装,需要通过回调函数来实现
当处于pending状态时(异步等待中),我们创建一个新的promise对象。给未来会产生的成功和失败状态添加上回调函数。
class myPromise {
constructor(fn) {
if (typeof (fn) !== 'function') {
throw TypeError(`myPromise resolve ${fn} is not a function`)
}
this.status = 'pending'
this.data = undefined
this.resolveCB = null
this.rejectCB = null
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'
this.data = value
// console.log(this.resolveCB)
this.resolveCB && this.resolveCB()
}
}
let reject = (value) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.data = value
this.rejectCB && this.rejectCB()
}
}
fn(resolve, reject)
}
then(resolveFn, rejectFn) {
if (this.status === 'resolved') {
if (typeof resolveFn !== 'function') {
return myPromise.resolve(this.data)
} else {
let res = resolveFn(this.data)
if (res instanceof myPromise) {
return res
} else {
return myPromise.resolve(this.data)
}
}
}
if (this.status === 'rejected') {
if (typeof rejectFn !== 'function') {
return myPromise.reject(this.data)
} else {
let res = rejectFn(this.data)
if (res instanceof myPromise) {
return res
} else {
return myPromise.resolve(this.data)
}
}
}
if (this.status === 'pending') {
return new myPromise((resolve, reject) => {
this.resolveCB = ((resolveFn) => {
return () => {
let res = resolveFn(this.data)
if (res instanceof myPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
}
})(resolveFn)
this.rejectCB = ((rejectFn)=>{
return ()=>{
let res = rejectFn(this.data)
if(res instanceof myPromise){
res.then(resolve,reject)
}else{
reject(res)
}
}
})(rejectFn)
})
}
}
static resolve(data) {
return new myPromise((resolve, reject) => {
resolve(data)
})
}
static reject(data) {
return new myPromise((resolve, reject) => {
reject(data)
})
}
}
当执行结果处于异步的时候(主线程代码执行完才会去操作异步),异步传回一个状态,这时候就需要给当前状态添加回调函数。回调函数的内容是改变pending状态。回调函数会执行新的pormise。这时,新的promose状态就确定了,把新的peomise状态传出。实现异步操作。
我又回来了!
这里我们再来简单的重构下promise代码(网上借鉴,嘿嘿。)
我们先来实现promise同步状态的代码,这对异步的理解很更好的实现,毕竟饭要一口一口吃,代码要按逻辑啃。(这里代码没做优化处理,主要实现核心功能)
class myPromise {
constructor(fn) {
this.value = undefined;
this.status = "pending";
fn(
value => {
this.status = "resolve"
this.value = value;
},
reason => {
this.status = "rejected";
this.value = reason;
}
);
}
then(onResolved) {
onResolved(this.value);
}
}
// 测试
var p = new myPromise((resolve, reject) => {
reject(11)
});
p.then(value => {
console.log("value", value);
});
myPromise接收一个函数参数,我们设置了初始化的pending状态,以及初始的 value 值。函数fn接收两个回调函数。第一个参数状态为resolve,第二个为reject,fn内部自身调用执行,then方法也接收一个回调函数,内部并自身调用执行。上面代码基本就实现了同步的promise状态确定。
class myPromise {
constructor(fn) {
this.value = undefined;
this.status = "pending";
this.children = []; // children为数组队列,存放多个回调函数
fn(
value => {
this.status = "resolve";
this.setValue(value);
},
reason => {
this.status = "rejected";
this.setValue(reason);
}
);
}
then(onResolved) {
this.children.push(onResolved);
}
setValue(value) {
this.value = value;
this.children.forEach(child => {
child(this.value);
});
}
}
我们在来看异步实现代码,因为状态是异步的,所以要主线程代码执行完毕才会去执行异步代码。这里添加了一个回调函数数组,目的是异步执行了来触发回调函数,
我们来梳理下状态流程,异步初始状态为pending,promise执行完,但是状态没有确定,我们继续执行主线程代码,这时候我们将then方法中的回调函数push进回调函数数组,主线程代码执行完毕,异步状态获取了,此时执行对应回调函数的回调函数,这样就实现了异步的调用,
其实还没有我们想的那么简单,这里还要涉及到异步中的宏任务与微任务,(promise.then()优先于其他异步任务执行
以及对setTimeout(fn,0)的应用)
未完待续。。。。。。
资料来源
https://juejin.im/post/5be93f6ff265da6153044d88
深入学习:
https://github.com/xieranmaya/blog/issues/3