Promise
是js中的一个重要模块,它可以方便我们更优雅地管理异步回调问题,彻底解决了回调地狱问题,并且其核心代码很短。了解Promise
的运作原理和机制,会有莫大的好处,就算转变了编程语言,只要还有异步这个概念,就可以自己动手封装。
Promise
核心功能之一就是管理回调任务,在异步任务结束后,通过调用resolve方法,实现调用回调并且传递参数:
class MyPromise {
constructor(fn) {
this.callback = null;
fn(this.resolve.bind(this));
}
resolve(arg) {
this.callback(arg);
}
done(handle) {
this.callback = handle;
}
}
这算是最最最简易的Promise了,运行一下看结果:
new MyPromise((res) => {
setTimeout(() => {
res("1秒过去了");
}, 1000);
}).done((value) => {
console.log(value);
})
结果:
1秒过去了
运行结果没有问题,一秒后输出了res中的参数,但是这样的Promise是根本不够用的。
思考这样一个问题:
如果童鞋们对Promise使用很多的的话,就可能会发现这样的现象:
现在new了一个Promise对象,丢给了它一个异步任务,然后我去楼下吃了顿饭,等到我回来时,异步任务早已完成,这时我再调用then
方法,它仍然能够给出结果。
可见上述的简易版并不具有这样的功能,于是对它进行改造:
class MyPromise {
constructor(fn) {
this.state = "PENDING"; //添加状态
this.value = null; //保存结果
this.callback = null;
fn(this.resolve.bind(this));
}
resolve(arg) {
this.state = "RESOLVED";
this.value = arg; //保存结果
this.handle(this.callback);
}
done(callback) {
this.handle(callback);
}
handle(callback) {
if(this.state === "PENDING") {
this.callback = callback;
return;
}
callback(this.value);
}
}
上述代码增加了状态和保存了结果,如果resolve被调用,就改变状态并保存结果,在调用done方法时,如果已经是RESOLVED状态,则直接调用回调:
let test = new MyPromise((res) => {
setTimeout(() => {
res("都第3秒了我才输出");
}, 1000);
});
setTimeout((() => {
console.log("2秒过去了");
}), 2000);
setTimeout(() => {
test.done((value) => {
console.log(value);
});
}, 3000);
结果:
2秒过去了
都第3秒了我才输出
上述代码解决了之前提到的问题,但是到现在为止,Promise最大的魅力还没有体现出来。
要实现Promise的链式调用,就必须搞清楚链式调用的本质,先拿我们常用的Promise举例,如果童鞋们的学习没有偷懒的话,应该可以知道then方法发挥的是一个Promise对象,同时回调的函数不止一个,所以可以这样修改代码:
class MyPromise {
constructor(fn) {
this.state = "PENDING";
this.value = null;
this.callbacks = []; //改为数组
fn(this.resolve.bind(this));
}
resolve(arg) {
this.state = "RESOLVED";
this.value = arg;
this.callbacks.forEach(callback => {
this.handle(callback); //迭代调用队列中所有的回调
})
}
done(callback) {
this.handle(callback);
return this; //返回本身
}
handle(callback) {
if(this.state === "PENDING") {
this.callbacks.push(callback); //将回调压入队列
return;
}
if(callback) {
callback(this.value);
}
}
}
上述代码在done方法中返回了this,即Promise对象本身,现在我们来试一下结果吧!
new MyPromise((res) => {
setTimeout(() => {
console.log(1);
res()
}, 1000)
}).done(() => {
console.log(2);
}).done(() => {
console.log(3);
})
结果:
1
2
3
上述代码实现了最低限度的链式调用但是距离我们想要的还差很远。
设想这样的一个场景:
new MyPromise((res) => {
setTimeout(() => {
res(1)
}, 1000)
}).done((data) => {
console.log(data);
return 2;
}).done((data) => {
console.log(data);
return 3;
}).done((data) => {
console.log(data);
})
这个例子中,每次链式调用都会返回不同的结果,然而之前的代码并不能实现这种功能,所以说每次done方法都必须返回一个新的Promise对象,而不是他自己,并且这个新Promise对象要隐式地被RESOLVED
:
class MyPromise {
constructor(fn) {
this.state = "PENDING";
this.value = null;
this.callbacks = [];
fn(this.resolve.bind(this));
}
resolve(arg) {
this.state = "RESOLVED";
this.value = arg;
this.callbacks.forEach(callback => {
this.handle(callback);
})
}
done(callback) {
return new MyPromise((res) => { //返回一个新的MyPormise
this.handle({
onfinished: callback,
resolve: res, //将返回的MyPromise的resolve方法传递给handle方法
})
});
}
handle(callback) {
if(this.state === "PENDING") {
this.callbacks.push(callback);
return;
}
if(callback) {
let r = callback.onfinished(this.value); //获得回调的返回值
callback.resolve(r); //通过resolve传递给下一个done方法
}
}
}
着重分析一下改动的部分:
首先在done方法里返回一个新的Promise实例,为了能够操控这个新的实例,将它的resolve方法一并保存,然后在handle里通过调用它来将callback的返回值作为参数传递给下一条链子:
new MyPromise((res) => {
setTimeout(() => {
res(1)
}, 1000)
}).done((data) => {
console.log(data);
return data + 1;
}).done((data) => {
console.log(data);
return data + 1;
}).done((data) => {
console.log(data);
})
结果:
1
2
3
准备好了吗,接下来就要实现最终奥义:异步任务的链式调用。
通过现有的代码,我们可以发现,如果返回值是一个MyPromise那么它将会被传至下一个链子的参数中,通过这个现象,向上溯源,我们可以在上层调用的resolve中拿到这个值加以判断。其次就是如何把这个MyPromise中的reslove的参数传递下去和如何让新的异步任务完成后再进行下一步了。
先看实现:
class MyPromise {
constructor(fn) {
this.state = "PENDING";
this.value = null;
this.key = "Promise"; //便于判断师是否为Promise对象
this.callbacks = [];
fn(this.resolve.bind(this));
}
resolve(arg) {
if(typeof arg === "object" && arg.key === "Promise") { //判断
let done = arg.done; //获得回调中返回的Mypromise实例的done方法
done.call(arg, this.resolve.bind(this)); //手动调用done方法,并将当前Promise的resolve方法作为callback传递
return; //中止
}
this.state = "RESOLVED";
this.value = arg;
this.callbacks.forEach(callback => {
this.handle(callback);
})
}
done(callback) {
return new MyPromise((res) => {
this.handle({
onfinished: callback,
resolve: res,
})
});
}
handle(callback) {
if(this.state === "PENDING") {
this.callbacks.push(callback);
return;
}
if(callback) {
let r = callback.onfinished(this.value);
callback.resolve(r);
}
}
}
这次改动比较难以理解,首先要获得返回的新实例的done方法,然后手动调用它,将当前Promise的reslove方法传入作为新实例的回调,当新实例RESOLVED后,新实例就会调用它的回调,也就是外层Promise的reslove方法,并将结果作为参数传入,这样就实现了外部Promise的解决依赖于内部Promise的解决,并且可以正常传递参数:
new MyPromise((res) => {
setTimeout(() => {
res("1秒过去了")
}, 1000)
}).done((data) => {
console.log(data);
return new MyPromise((res) => {
setTimeout(() => {
res("2秒过去了");
}, 1000);
});
}).done((data) => {
console.log(data);
return new MyPromise((res) => {
setTimeout(() => {
res("3秒过去了");
}, 1000);
});
}).done((data) => {
console.log(data);
});
结果:
1秒过去了
2秒过去了
3秒过去了
Promise可以说是js中很经典的一个面试题了,同时它的难度还是有的,弄懂他不仅能提高使用js的技巧性,还能由小小的成就感o( ̄▽ ̄)ブ。