Promise是什么
Promise是ES6中新增的内置类,主要作用是用来规划异步编程
let p = new Promise([executor])
Promise是一个内置类p是类的一个实例[executor]可执行的回调函数(必须传递)
回调地狱问题
回调函数就是将一个函数作为参数,传递给另一个函数,在函数执行的时候,将传入的回调函数进行处理
回调地狱:回调函数嵌套回调函数
let callName = function(name,callback){
setTimeout(()=>{
console.log(name)
callback();
},1000)
}
callName('first',()=>{
callName('second',()=>{
callName('third',()=>{
console.log('end')
})
})
})
// 输出: first second third end
- 这个问题在
AJAX异步请求数据上尤为常见
当多个请求之间有相互依赖关系时,上一个请求完成执行才能执行下一个请求,这就是
AJAX请求串行
// 以 jQuery 的 ajax 方法为例
let data = {};
$.ajax({
url: '/api/info',
success: function (result) {
// 获取数据成功后执行回调函数
// result就是从服务器获取的结果
data = result;
$.ajax({
url: `/api/score?id=${data.id}`,
success: function (result) {
$.ajax({
url: `/api/ranking?val=${result.chinese}`,
success: function (result) {
}
});
}
});
}
});
// 上述代码需要在拿到`个人信息`后,再拿`成绩`,再拿语文`成绩排名`
- 基于
Promise解决上述回调地狱的问题
function queryInfo() {
return new Promise(resolve => {
$.ajax({
url: '/api/info',
success: function (result) {
resolve(result);
}
});
});
}
function queryScore(id) {
return new Promise(resolve => {
$.ajax({
url: `/api/score?id=${id}`,
success: function (result) {
resolve(result);
}
});
});
}
function queryPaiMing(val) {
return new Promise(resolve => {
$.ajax({
url: `/api/ranking?val=${val}`,
success: function (result) {
resolve(result);
}
});
});
}
// 基于Promise解决回调地狱
queryInfo().then(result => {
return queryScore(result.id);
}).then(result => {
return queryPaiMing(result.chinese);
}).then(result => {
// 获取的排名信息
});
Promise类
Promise是类所以需要通过
new关键字执行,并传入一个executor可执行的回调函数作为参数
Promise是异步的?
如果Promise是异步的,那么下面的例子中执行的结果就是 "2 1" ,但是执行结果却是"1 2"
let p1 = new Promise(()=>{
console.log('1');
})
console.log('2')
从上述代码我们可以总结出,
Promise本身是同步的,但是他是用来管理异步编程的,new Promise的时候会立即将executor函数执行
Promise实例的属性
那为什么有些人会认为Promise是异步的呢?
我们将p1在控制台中打印出来,会发现这个Promise实例上除了原型链之外还有两个私有属性,分别是
[[PromiseState]]以及[[PromiseResult]]
[[PromiseState]]:用于存储Promise实例状态pending初始状态fulfilled/resolved成功状态(一般指的是异步请求成功,在不同浏览器版本中,成功返回的值不同,但是不会影响结果)rejected失败状态(一般指的是异步请求失败)
[[PromiseResult]]:Promise结果或者值- 初始值是undefined
- 不论成功的结果还是失败的原因都会存储在这个值里面
executor可执行回调
- 第一个参数:
resolve函数--函数执行会修改Promise实例的状态为成功fulfilled/resolve - 第二个参数:
reject函数--函数执行会修改Promise实例的状态为失败reject - 通常在
executor函数中管理一个异步操作,异步操作成功执行resolve函数,让实例状态修改成成功,并且获取成功的结果;异步操作失败执行reject函数,让实例状态修改成失败,并获取失败原因 - 只要Promise实例的状态从
pending变为fulfilled或者rejected,则状态就不能再改变了,即实例状态是不可逆
// new Promise(executor)
let p1 = new Promise((resolve,reject)=>{
resolve(10)
})
Promise.prototype
then([A],[B]):基于then方法存放两个回调函数A/B,当Promise状态为成功时执行A,状态为失败时执行B,并将[[PromiseResult]]的值传递给对应函数catchfinally
then
let p1 = new Promise((resolve,reject)=>{
resolve('OK');
});
p1.then(result=>{
console.log("成功" + result);
},reason=>{
console.log("失败" + reason)
})
then方法也是同步操作,但是then的callback函数是异步的,then方法执行可以理解为将then中的callback函数存储在p1实例中,在Promise中resolve函数执行之后会通知存储在p1实例中对应的方法执行
执行then方法会返回一个新的Promise实例
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
let ran = Math.random();
ran < 0.5 ? reject("NO") : resolve("OK");
},1000);
});
let p2 = p1.then(A=>{
console.log("成功"+A);
return A + "1"
},B=>{
console.log("失败"+B);
return B + "0"
});
- 执行
then方法是为了将传递给then的成功的回调以及失败的回调存储在p1中 - 同时
返回一个新的Promise实例 p2- 新实例p2的状态是由上一个实例基于then方法存放的A/B函数所决定
- 不论是A还是B执行,只要
执行不报错,则p2的状态就是成功,只要报错状态就是失败 - p2的结果是A/B函数执行的返回值,或者是报错的失败原因
- 特殊情况:如果A/B返回的是一个新的Promise实例,则返回的Promise实例的成功失败以及结果,直接决定p2的状态和结果
p1的成功和失败也会受到executor执行是否报错影响,执行报错,则p1的状态是失败的,PromiseResult的值就是失败的原因;如果执行不报错,再看执行的是resolve还是reject
new Promise((resolve, reject) => {
// resolve(10); //=>执行resolve函数时输出 10 100 1000
reject(20); //=>执行reject函数时的输出 20 2 20
}).then(result => {
console.log("成功:" + result);
return result * 10;
}, reason => {
console.log("失败:" + reason);
return reason / 10;
}).then(result => {
console.log("成功:" + result);
return Promise.reject(result * 10);
}, reason => {
console.log("失败:" + reason);
return reason / 10;
}).then(result => {
console.log("成功:" + result);
return result * 10;
}, reason => {
console.log("失败:" + reason);
return reason / 10;
});
resolve函数执行时会一直执行then中传入的第一个回调函数,而reject函数执行的时候将Promise实例的状态修改成失败,所以在第一个then中会执行传入的第二个函数,而第二个函数没有报错,则会直接将新的Promise实例的状态修改成成功,而后的所有then方法都是执行传入的第一个callback
then的顺延
then([A],[B]):如果其中一个函数没有传递,则会
顺延,即[A]函数没有传递则顺着then链找下一个then的[A];,即[B]函数没有传递则顺着then链找下一个then的[B];
Promise.resolve(10).then(null, reason => {
console.log("失败:" + reason);
return reason / 10;
}).then(result => {
console.log("成功:" + result); //=> 成功:10
return result * 10;
}, reason => {
console.log("失败:" + reason);
return reason / 10;
})
当then中[A]没有传递,他会默认
result=>{ return Promise.resolve(result); },当then中[B]没有传递,他会默认reason=>{ return Promise.reject(reason); }
catch
明白了顺延之后,catch就相当于 then( null , reason=>{ ... } ),也就是then不传第一个参数。
// 真实项目中:then只是处理成功的 catch处理失败(一般写在最后)
Promise.reject(10).then(result => {
console.log(`成功了 -> ${result}`);
}).catch(reason => {
console.log(`失败了 -> ${reason}`);
});
在then链中只要报错的位置,和catch之间的then中没有传递第二个callback,都会直接将错误的原因顺延到catch,所以我们在真实项目中一般不会在then中传递第二个callback,防止传递之后将异常拦截,造成后期没必要的麻烦
返回失败但是没有失败处理
浏览器会在控制台报错,但是该错误不会阻塞代码的执行
Promise.reject(10).then(result=>{
console.log(`成功了--${result}`);
return result * 10;
})
setTimeout(()=>{
console.log(1)
},1000)
直接在then链的尾部加一个catch(reson=>{})就可以解决这个报错
Promise的静态方法
resolve返回一个状态为成功的Promise实例reject返回一个状态为失败的Promise实例all管理多个promise实例- 当所有实例都为成功,
Promise.all返回的总状态的实例才是成功的,结果是一个数组,按照顺序依次存储每一个实例成功的结果 - 当有一个实例的结果是失败的,总实例的结果也是失败的,结果是当前实例失败的原因
- 当所有实例都为成功,
race管理多个promise实例,以返回运行最快的那个实例的结果,不过是成功还是失败
手写Promise
想要深入理解
Promise,Promise的源码就一定要清楚
Promise类
function myPromise(executor){
// 参数验证,保证executor是一个可执行的函数
if(typeof executor !== 'function') throw new TypeError("myPromise resolver undefined is not a function")
// 实例上挂在的属性
this.myPromiseState = 'pending';
this.myPromiseValue = undefined;
this.resolveFunc = function(){};
this.rejectFunc = function(){};
var _this = this;
var change = function(state,value){
//实例的状态不为pending的时候才修改状态
if(_this.MyPromiseState !== "pending") return;
_this.MyPromiseState = state;
_this.MyPromiseValue = value;
//通过定时器来模拟异步的效果
let timer = setTimeout(()=>{
_this.MyPromiseState === "fulfilled" ?
_this.resolveFunc(_this.MyPromiseValue):
_this.rejectFunc(_this.MyPromiseValue);
},0)
}
var resolve = function(){
change("fulfilled",result);
};
var reject = function(){
change("rejected",reson);
};
// 立即执行 executor 函数
try{
executor(resolve,reject);
}catch(err){
reject(err)
}
}
myPromise.resolve/reject
MyPromise.resolve = function resolve(result){
return new MyPromise(resolve=>{
resolve(result);
})
}
MyPromise.reject = function reject(reason){
return new MyPromise((_,reject)=>{
reject(reason);
})
}
myPromise.prototype.then()- 每一次执行then方法都会返回一个新的Promise实例
- 上一个Promise实例的resolve/reject的执行,控制新返回的实例的成功和失败
MyPromise.prototype.then = function then(resolveFunc,rejectFunc){
//实现顺延效果
if(typeof resolveFunc !== "function"){
resolveFunc = function resolveFunc(result){
return MyPromise.resolve(result);
}
}
if(typeof rejectFunc !== "function"){
rejectFunc = function rejectFunc(reason){
return MyPromise.reject(reason);
}
}
return new myPromise((resolve,reject)=>{
//执行不报错,返回新实例的状态是成功的(如果返回值是新Promise实例,则该实例的状态的成功和失败决定了返回实例的状态)
_this.resolveFunc = function(result){
try {
var x = resolveFunc(result);
x instanceof Promise ?
x.then(resolve,reject) :
resolve(x);
} catch (err) {
reject(err)
}
}
_this.rejectFunc = function(reason){
try {
var x = rejectFunc(reason);
x instanceof Promise ?
x.then(resolve,reject) :
resolve(x);
} catch (err) {
reject(err)
}
}
})
}
myPromise.prototype.catch()
catch就相当于p1.catch([function]) => p1.then(null,[function])
MyPromise.prototype.catch = function catch(rejectFunc){
return this.then(null,rejectFunc)
}
MyPromise.allMyPromise.all返回的也是MyPromise实例p- 数组中的
每个MyPromise实例都是成功的,最后p才是成功的 - 数组中只要有
一个实例是失败的,最后p都是失败的 - 如果最后都是成功,p的
MyPromiseValue存储的也是一个数组:按照之前存放MyPromise实例的顺序,存储每一个实例的结果
myPromise.all = function all(promiseAry){
return new myPromise((resolve,reject)=>{
let result = [],
index = 0;
let fire = function fire(){
if(index === promiseAry.length){
resolve(result);
}
}
for(let i = 0; i < promiseAry.length; i++){
let item = promiseAry[i];
if(!(item instanceof myPromise)){
result[i] = item;
index++;
fire();
return;
}
item.then(result=>{
result[i] = result;
index++;
fire();
},reason=>{
reject(resson); // 只要有一个函数是失败的,整体结果就是失败的
})
}
})
}