Promise由浅入深
前言
我们都知道javaScript是单线程的,代码执行的顺序是从上往下。但是在实际开发中难免会出现异步编程。所谓异步就是代码执行到这里不会立即执行得出结果,比如ajax请求,文件的读取等,为了不阻塞进程,先把它放到事件队列中。
但是传统的ajax的请求方式有一个很大问题,比如有多个请求并且下次的请求需要依赖上次的请求结果,那么就会出现多层嵌套,也就是所谓的“回调地狱”。
es6中提出了Promise。可以作为解决异步的一种方式。promise可以理解成是一个对象的代理,通常用来代理一个未来的值,为什么说是未来呢,因为这个值通常是由异步操作得出的,不像同步操作那样立即就可以拿到值。
我们可以把Promise比做一个容器,它里边包含着未来才会结束的事件的结果。
Promise有三种状态:
- pending 初始状态
- fulfilled 成功完成
- rejected 失败
1、 声明未执行的Promise实例是pending状态,执行成功调用resolve方法是fulfilled状态,执行失败调用rejecte方法是rejected状态。
2、 Promise状态一旦改变,就不会在变了。也就是说状态的改变只有两种可能:(1)从pending变成fulfilled (2)从pending变成rejected。只要状态一旦改变,状态就凝固了,不会在发生改变。
Promise参数
Promise接受一个函数参数,函数参数又有两个参数,分别是resolve和rejecte。在函数参数中执行相应的异步操作,当执行成功,调用resolve,执行失败调用rejecte。通过reslove和rejecte传递结果。
Promise的前因后果
new Promise
var promise = new Promise(function (resolve, reject) {
console.log('create promise')
setTimeout(function () {
resolve('first value');
console.log('最后执行');
}, 1000);
})
// 实例创建完成,函数内部有异步操作,不会立即执行resolve方法,所以状态为pending
console.log(promise)
输出先后顺序:
"create promise"
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
"最后执行"
从输出的先后顺序可以看出来Promise的创建以及函数参数的执行是一个同步的过程,实例创建完成后由于函数参数中有一个异步操作,不会立即执行resolve方法,所以Promise的实例仍是pending状态,。
Promise实例构造函数的原型上有then方法和catch方法,可以被其实例所共享。其中then方法接受两个函数参数,第一个函数的参数就是resolve中接收的值,第二个函数是错误处理;catch方法是rejecte中的值。
Promise实例调用then方法的实现思路:
// 伪代码
Promise.prototype.then = function(success, faile){
// then方法的伪代码 执行相应的函数参数
// 如果异步操作成功执行success方法
success();
// 反之
faile();
// 最后返回一个新的Promise对象
return newPromise;
}
链式调用
因为then方法的两个函数参数默认会返回一个新的Promise对象,所以可以进行链式调用。调用规则:
- 当没有显式return时,默认返回新的Promise对象;
- 当return的不是Promise对象时,那么return的值会作为then返回的新Promise实例的resolved参数;
- 当return的是一个Promise实例时,那么then返回的Promise实例就是该实例;
具体如下:
promise.then(function (response) {
console.log(response); // first value
console.log(promise); // Promise对象 状态已经由pending 变为 resolved
}).then(function (response) {
console.log(response); // undefined
// 从这里可以看出来,then()方法返回的是一个新的promise,因为新的Promise实例中并没有执行resolve()方法,更没有什么异步操作,所以这里的值是undefined(声明未初始化)
// 手动创建并返回一个新的Promise实例
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('second value')
}, 1000);
})
}).then(function(response){
console.log(response); // second value
// 这里之所以能够获取到值,是因为上一个then()返回的Promise成功执行异步操作
return '还可以返回非Promise实例'
}).then(function(response){
console.log(response) // 还可以返回非Promise实例
})
下面通过简单的示例一步步深入了解。
一、Promise立即执行
var promise = new Promise(function(resolved, rejected){
console.log('创建promise')
resolved('成功');
rejected('失败');
})
console.log('执行完成')
promise.then(function(res){
console.log(res);
}).catch(function(err){
console.log(err);
})
输出结果:
创建Promise
执行完成
成功
解释:Promise对象表示将来某一时刻要发生的事件,但是创建(new)Promise是一个同步的过程,包括传入的函数参数也是一个立即执行的函数,只是函数内部是异步操作还是同步操作就不确定了。所以以上代码是按顺序输出。
二、Promise的三种状态
var p1 = new Promise(function(resolve,reject){
resolve(1);
});
var p2 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve(2);
}, 500);
});
var p3 = new Promise(function(resolve,reject){
setTimeout(function(){
reject(3);
}, 500);
});
p1、p2、p3的状态
console.log(p1); // resolved
console.log(p2); // pending
console.log(p3); // pending
setTimeout(function(){
console.log(p2); // resolved
}, 1000);
setTimeout(function(){
console.log(p3); // rejected
}, 1000);
p1.then(function(value){
console.log(value); // 1
});
p2.then(function(value){
console.log(value); // 2
});
p3.catch(function(err){
console.log(err); // 3
});
输出结果:
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
1
2
3
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 2}
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 3}
解释:其实我们可以把Promise内部理解成是一个状态机,分别有pending resolved rejected三种状态。 当刚创建Promise时处于pending状态,所以p2,p3的状态为pending,因为p1的函数参数执行的是同步代码,Promise刚创建完成resolved也就执行了,所以p1是resolved状态。然后又是两个setTimeout函数,所以会先分别执行对应的then方法,输出 1、 2、 3;1s后执行setTimeout函数,此时p2 p3的状态分别已经变成了resolved和rejected。
三、Promise状态不可逆
var p1 = new Promise(function(resolve, reject){
resolve("success1");
resolve("success2");
});
var p2 = new Promise(function(resolve, reject){
resolve("success");
reject("reject");
});
p1.then(function(value){
console.log(value);
});
p2.then(function(value){
console.log(value);
});
输出结果:
'success1'
'success'
解释:Promise状态一旦从pending变成resolved或rejected时状态就凝固了。不管再调用resolved或rejected都不管用。
四、链式调用
var p = new Promise(function (resolve, reject) {
resolve(1);
});
p.then(function (value) {
console.log(value); // 1
return value * 2;
}).then(function (value) {
console.log(value); // 2
}).then(function (value) {
console.log(value); // undefined
return Promise.resolve('resolve');
}).then(function (value) {
console.log(value); // resolve
return Promise.reject('reject');
}).then(function (value) {
console.log('resolve: ' + value);
}).catch(function(err){
console.log("rejected: ", err) // reject
})
输出结果:
1
2
"undefined"
"resolved"
"rejected: rejected"
解释: Promise对象的then方法返回一个新的Promise对象,因此可以链式调用。第一个then有返回值,所以在二个then中输出2;而第二个then没有返回值,所以第三个then输出undefined;第三个then返回一个Promise的resolved方法,所以第四个then中输出resolved;但是第四个then中返回Promise的rejected方法,所以第五个then的第一个函数参数不会输出,而是在第二个函数参数或者catch中输出rejected。
五、Promise then()回调的异步性
var promise = new Promise(function(resolved, rejected){
resolved('success')
console.log('first')
})
promise.then(function(res){
console.log(res)
})
console.log('second')
输出结果:
'first'
'second'
'success'
解释:new Promise、函数参数以及其内部的代码是一个同步的过程,所以会先输出 first,但是then方法中的回调函数是异步的,所以先输出 second,最后输出 success。
六、Promise中的异常
var p1 = new Promise( function(resolve,reject){
foo.bar();
resolve( 1 );
});
console.log(p1)
// Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: ReferenceError: foo is not defined}
p1.then(
function(value){
console.log('p1 then1 value: ' + value);
},
function(err){
console.log('p1 then1 err: ' + err);// 报错 foo is not defined
// 没有显示返回
}
).catch(function(err){
console.log('err', err)
}).then(
function(value){
console.log('p1 then2 value: '+value); // p1 then2 value: undefined
},
function(err){
console.log('p1 then2 err: ' + err);
}
);
var p2 = new Promise(function(resolve,reject){
resolve( 2 );
});
p2.then(
function(value){
console.log('p2 then1 value: ' + value);// p2 then1 value: 2
foo.bar(); // 报错
},
function(err){
console.log('p2 then1 err: ' + err);
}
).then(
function(value){
console.log('p2 then2 value: ' + value);
},
function(err){
console.log('p2 then2 err: ' + err); // p2 then2 err: foo is not defined
return 1;
}
).then(
function(value){
console.log('p2 then3 value: ' + value);// p2 then3 value: 1
},
function(err){
console.log('p2 then then then err: ' + err);
}
);
输出结果:
p1 then1 err: ReferenceError: foo is not defined
p2 then1 value: 2
p2 then2 err: ReferenceError: foo is not defined
p1 then2 value: undefined
p2 then3 value: 1
解释:Promise中的异常由then参数中第二个回调函数(Promise执行失败的回调)处理,异常信息将作为Promise的值。异常一旦得到处理,then返回的后续Promise对象将恢复正常,并会被Promise执行成功的回调函数处理。另外,p1、p2 多个then的回调是交替执行的 ,这正是由Promise then回调的异步性决定的。
七、Promise.resolve()
Promise.resolve(value)返回给定给定值解析后的Promise对象。
value分三种情况:
- 普通值 --- 以该值作为resolved状态返回promise
Promise.resolve("Success").then(function(value) {
console.log(value); // "Success"
}, function(value) {
// 不会被调用
});
- Promise对象 --- 返回值即为该传入的Promise对象
let p1 = Promise.resolve('success');
let p2 = Promise.resolve(p1);
cast.then(value => {
console.log('value: ' + value);
});
console.log('p1 === p2 ? ' + (p1 === p2));
/*
* 打印顺序如下,这里有一个同步异步先后执行的区别
* p1 === p2 ? true
* value: success
*/
- 带有then方法的对象 --- 返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled)
// Resolve一个thenable对象
var p1 = Promise.resolve({
then(onFulfill, onReject) {
onFulfill("fulfilled!");
}
});
console.log(p1 instanceof Promise) // true, 这是一个Promise对象
p1
.then(res => {
console.log(res); // "fulfilled!"
})
.catch(err => {
console.log(err)
})
// Thenable在callback之前抛出异常
let thenable = {
then(resolve) {
throw new Error("Throwing");
resolve("Resolving")
}
};
let p2 = Promise.resolve(thenable);
p2
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err) // Throwing
})
// Thenable在callback之后抛出异常
let thenable = {
then(resolve) {
resolve("Resolving");
throw new Error("Throwing");
}
}
let p3 = Promise.resolve(thenable);
p3
.then(res => {
console.log(res) // Resolving
})
.catch(err => {
console.log(err)
})
let thenable = {
then(resolve) {
resolve("Resolving");
throw new Error("Throwing");
}
}
let thenable = {
then(resolve, reject) {
reject("Oops")
}
}
let p4 = Promise.resolve(thenable);
p4
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err) // oops
})
- resolve异步解析Promise对象
let p1 = Promise.resolve( 1 );
let p2 = Promise.resolve( p1 );
let p3 = new Promise(function(resolve, reject){
resolve(1);
});
let p4 = new Promise(function(resolve, reject){
resolve(p1);
});
console.log(p1 === p2); // true
console.log(p1 === p3); // false
console.log(p1 === p4); // false
console.log(p3 === p4); // false
p4.then(function(value){
console.log('p4=' + value);
});
p2.then(function(value){
console.log('p2=' + value);
})
p1.then(function(value){
console.log('p1=' + value);
})
输出结果:
true
false
false
false
p2=1
p1=1
p4=1
解释:Promise.resolve()中的参数可以是一个普通值或者一个Promise对象,如果是普通值,则它返回一个Promise对象,PromiseValue就是该值;如果参数是Promise对象,那么直接返回这个Promise对象参数。所以p1==p2。但是因为通过new的方式创建的Promise对象都是一个新的对象,所以其余都是false。
为什么p4的then最先调用,但在控制台上是最后输出结果的呢?因为p4的resolve中接收的参数是一个Promise对象p1,resolve会对p1”提取“,获取p1的状态和值,但这个过程是异步的。也就是说Promise中的resolve方法会对接收的Promise对象进行“分解”。
八、resolve vs reject
var p1 = new Promise(function(resolve, reject){
resolve(Promise.resolve('resolve'));
});
var p2 = new Promise(function(resolve, reject){
resolve(Promise.reject('reject'));// 拆箱获取值 reject
});
var p3 = new Promise(function(resolve, reject){
reject(Promise.resolve('resolve'));
});
p1.then(
function (value){
console.log('p1', value); // p1 resolve
console.log('p1 fulfilled: ' + value); //p1 fulfilled resolve
},
function (err){
console.log('p1 rejected: ' + err);
}
);
p2.then(
function (value){
console.log('p2', value)
console.log('p2 fulfilled: ' + value);
},
function (err){
console.log('p2 err', err) // p2 err reject
console.log('p2 rejected: ' + err); //p2 fulfilled reject
}
);
p3.then(
function (value){
console.log('p3', value)
console.log('p3 fulfilled: ' + value);
},
function (err){
console.log(err) // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
console.log('p3 rejected: ' + err); // rejected [object Promise]
}
);
setTimeout(function() {
console.log(p1)
console.log(p2)
console.log(p3)
}, 100);
输出结果:
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
p3 rejected: [object Promise]
p1 resolve
p1 fulfilled: resolve
p2 err reject
p2 rejected: reject
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
{[[PromiseStatus]]: "rejected", [[PromiseValue]]: "reject"}
{[[PromiseStatus]]: "rejected", [[PromiseValue]]: Promise}
解释:
Promise回调中的第一个参数resolve,会对Promise执行"提取"。即当resolve的参数是一个Promise对象时,resolve会"提取"获取这个Promise对象的状态和值,(原封不动的返回传入的promise对象)但这个过程是异步的。
p1"提取"后,获取到Promise对象的状态是resolved,因此p1.then()中的第一个回调被执行;p2"提取"后,获取到Promise对象的状态是rejected,因此p2.then()中的第二个回调被执行。
但Promise回调函数中的第二个参数reject不具备”提取“的能力,reject的参数会直接传递给then方法中的第二个回调。因此,即使p3 reject接收了一个resolved状态的Promise,then方法中被调用的依然是rejected,并且参数就是reject接收到的Promise对象。
参考: MDN
阮一峰 Promise