1 为什么要使用Promise
防止回调黑洞
例如,正常的ajax调用,程序员既要定义业务逻辑函数,还要控制这些业务逻辑执行的顺序,一旦异步调用嵌套太深,整个代码可读性和可维护度都会下降。
例如,实际项目中有如下需求 0.登录 1.查用户配置,2.通过配置权限,查询可以浏览的数据,以上三步肯定是顺序执行的。如下就是直接采用回调方式来实现的代码,而且这里的代码,没有处理错误和异常。估计没几个人愿意看这么多括号的一大组逻辑。
function generalQuery(){
this.localVal = "hengebb";
let param = {
name:'hege',
key:'001'
}
var xhr = new XMLHttpRequest()
xhr.open('post', 'http://127.0.0.1:8080/ris/qcTest/lgin.api',JSON.stringify(param))
xhr.send(null)
xhr.onreadystatechange = ()=>{
if (xhr.readyState == 4 && xhr.status == 200) {
console.log("@2 local val ="+this.localVal)
let ret = xhr.responseText
let retObject = JSON.parse(ret);
if(retObject.token == '0001'){
let param = {
token:'0001',
}
var yhr = new XMLHttpRequest()
yhr.open('post','http://127.0.0.1:8080/ris/qcTest/config.api', JSON.stringify(param))
yhr.send(null)
yhr.onreadystatechange = function(){
console.log("@3 local val ="+this.localVal)
if(yhr.readyState == 4 && yhr.status == 200){
let ret = yhr.responseText
let retObject = JSON.parse(ret);
if(retObject.config == 0){
let param = {
token:"0001",
config:1
}
let zhr = new XMLHttpRequest()
zhr.open('post','http://127.0.0.1:8080/ris/qcTest/query.api', JSON.stringify(param))
zhr.send(null)
zhr.onreadystatechange = function(){
if(zhr.readyState == 4 && zhr.status == 200){
let ret = yhr.responseText
let retObject = JSON.parse(ret);
console.log(retObject.msg)
}
}
}
}
}
}
}
}
}
接下来,我们看Promise的处理方式
function login() {
let param = {
name:'hege',key:'001'
}
return new Promise((resolve)=>{
// 需要在这里处理异步任务
var xhr = new XMLHttpRequest();
xhr.open('post','http://127.0.0.1:8080/ris/qcTest/lgin.api',JSON.stringify(param));
xhr.send(null);
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
var ret = xhr.responseText;
// 成功的情况
resolve(ret);
}
}
})
}
function config(){
let param = {
token:'0001',
}
return new Promise((resolve)=>{
var xhr = new XMLHttpRequest();
xhr.open('post','http://127.0.0.1:8080/ris/qcTest/config.api',JSON.stringify(param));
xhr.send(null);
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
var ret = xhr.responseText;
// 成功的情况
resolve(ret);
}
}
})
}
function query(){
let param = {
token:"0001",
config:1
}
return new Promise((resolve)=>{
var xhr = new XMLHttpRequest();
xhr.open('post','http://127.0.0.1:8080/ris/qcTest/query.api',JSON.stringify(param));
xhr.send(null);
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200) {
// 获取后台数据
var ret = xhr.responseText;
// 成功的情况
resolve(ret);
}
}
})
}
定义完,每一步的操作后,执行代码如下
function promiseQuery(){
this.localVal = "hengebb";
login().then(ret=>{
return config();
}).then(ret=>{
console.log("@2 ==== this point "+this.localVal)
return query();
}).then(ret=>{
console.log(ret.msg)
})
}
2 Promise的实现原理
2.1 伪代码
function SelfPromise(fn) {
console.log("1. 注册异步函数")
this.value = null;
this.callbacks = [];
fn.call(this, this.resolve.bind(this))
//因为调用resolve函数的位置,在客户端,为了把this指针从客户端改变为SelfPromise实例对象,这里使用了bind函数
}
//自定义函数的原型函数
SelfPromise.prototype = {
constructor: SelfPromise,
then: function(thenFunc) {
console.log("3. 注册异步函数回调后,需要执行的函数")
var obj = {
onfulfilled: thenFunc
}
this.callbacks.push(obj);
console.log(this.callbacks)
},
resolve: function(data) {
this.value = data;
this.callbacks.forEach(item => { //类似执行一个函数链(职责链模式)
let p = item.onfulfilled(this.value);
console.log(this)
});
}
}
//客户端代码调用
function customPromiseTest(){
this.promiseTestVal = 99
let promise = new SelfPromise((resolve)=>{ //1. 将需要异步执行的函数注册到SelfPromise实例中,此函数的格式必须是带有一个形参function(param){}格式(这里是针对SelfPromise的)
console.log("2. 执行异步函数.... ")
//2. 执行注册到SelfPromise实例中的函数. 查看源码,第一步将resolve的this指针指向SelfPromise实例对象;第二部,通过call调用,执行fn异步注册函数
var param = {
ref_fac_id:1031
};
setTimeout(()=>{
console.log("异步回调开始...")
let ret = {
ret:1,name:"hege",token:"10088"
}
resolve(ret)
},1000)
}).then((ret)=>{ //3. 将异步返回后,需要后续执行的函数也注册到Promise的类中,
console.log("4. 回调过程调用在步骤3中注册的函数")
//4. 回调函数开始执行,在then调用时,注册的函数
console.log(ret.name + ret.token + " this." + this.promiseTestVal)
//使用()=>{}方式的函数,这种函数没有自己的构造器,所以,就没有this指针,就只能是用括号外部的this指针
})
}
在以上的代码中,我做了一些注释。
实际上,客户调用代码,首先将异步函数1和异步函数回调后,希望后续执行函数2,这两个函数注册到Promise中;Promise内部,含有一个类似包装器功能的函数resolve,用此函数后续调用一个函数链,这些函数链上的函数,就是通过then注册到内部的函数。
这里需要注意的点,还有如下几个,resolve函数的this指针必须bind到Promise函数中,因为执行的时候,是在客户端代码块内执行的。
另外,就是被注册函数最好都采用()=>{}方式,不要采用function的方式。 这样,在客户端的回调函数代码块中,就能直接使用客户端函数的this指针。
本人将以上的代码都上传到资源内,大家感兴趣的可以下载测试!