ES6原生提供了 Promise 对象。
当一个异步任务的执行需要依赖另一个异步任务的结果时,我们一般会将两个异步任务嵌套起来,但是使用回调函数来解决异步问题,简单还好说,但是发生很多次之后,这就是所谓的回调地狱,代码层层嵌套,环环相扣,逻辑稍微复杂一些,程序就会变得难以维护。
Promise,一定程度上解决了JavaScript的流程操作问题。Promise的用处,实际上是在于多重异步操作相互依赖的情况下,对于逻辑流程的控制。
先直接打印出来看看,console.dir(Promise)

直观的看到 ,Promise是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。这些方法都很眼熟吧
Promise是什么?
上面打印出来的结果,可以很清晰的看出 ,Promise是原生js 的一个类。
高版本浏览器默认支持。低版本有两种解决方案:1、可以自己手写,2、使用es6的库:es6-promise
1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列
4、代码风格,容易理解,便于维护
5、多个异步等待合并便于解决
promise 语法
promise 的意思就是“承诺”。表示:其他手段无法改变。
三种状态: 静待状态(初始状态) pending 、成功状态 fulfilled 、失败状态 rejected
【例1】既然是一个类,就 new一个 Promise 来看看
let p = new Promise((resolve,reject)=>{
console.log('同步操作')
setTimeout(() => {
resolve('异步操作,要传递的数据');
}, 1000);
})Promise 接收一个 executor执行函数 作为 参数(Promise的构造函数接收一个参数,是函数),并且传入两个参数:resolve,reject。当new的时候,这个执行函数立即执行。先执行同步代码,异步代码先放着。所以会先输出 ‘同步操作’,再去执行setTimeout()函数,而setTimeout这个函数是异步的在1秒后才会执行,并调用resolve函数。
resolve:调用该参数表示--从 静待态 转为 成功状态。reject:调用该函数时表示--从 静待态 转为 失败状态。这里可以暂时这么理解:resolve 理解为成功后要传递的数据,reject 理解为失败后要传递的数据。
Promise对象的状态改变只有两种:从 pending 变为 fulfilled 或者从 pending 变为 rejected。成功状态不能转为失败态,失败态不能转为成功态。只要发生状态转变的情况,那么该状态就凝固了,不会再变了,会一直保持这个结果。这两种状态只能有一个,不能同时存在。谁写在前,就执行谁
【 例2 】 这是同步操作
let p = new Promise((resolve,reject)=>{
resolve("成功了");
//reject('不努力');
})
p.then((value)=>{
//成功时
console.log('恭喜',value);//恭喜 成功了
},(rejected)=>{
//失败时
console.log('失败了',rejected);//失败了 不努力
})promise.then() 是 promise 最为常用的方法,实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
【 例 3 】 异步操作
let p = new Promise((resolve,reject)=>{
console.log('先执行同步的')
setTimeout(() => {
resolve('执行成功');
}, 2000);
})
p.then((value)=>{
//成功时
console.log('恭喜1',value);//恭喜1 执行成功
},(reason)=>{
//失败时
console.log('失败了1',reason);
})在上面的代码中,我们执行了一个异步操作,setTimeout(),2秒后,运行代码,会在2秒后输出“恭喜1 执行成功”,并且调用resolve方法。
resolve() 函数的作用----将Promise对象的状态从 “静待态” 变为 “成功态”(即从 pending 变为 resolved),在操作成功时调用,并将操作的结果,作为参数传递出去;
reject()函数的作用----将Promise对象的状态从 “静待态” 变为 “失败”(即从 pending 变为 rejected),在操作失败时调用,并将操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
then()方法可以接受 两个回调函数 作为参数。第一个 -- 当Promise对象的状态变为resolved时调用(成功时调用该函数),第二个 --- 当Promise对象的状态变为rejected时调用(失败时调用次函数)。其中,第二个参数是可选的,不一定要提供。这两个参数都接受Promise对象传出的值作为参数,并且能在回调函数中拿到成功的数据和失败的原因。
我们开看下面的示例代码:
注意:当new的时候,这个执行函数立即执行。
let p = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('异步操作')
resolve('执行成功'); //所以调用以后没有结果显示
}, 1000);
})运行一下,先执行setTimeout()这个异步操作,1秒后会再控制台上输出:‘异步操作’,并且调用resolve方法。我只是new了一个对象,并没有调用它,我传进去的函数就已经执行了,,然而在项目中,通常是当我们需要它运行时,再运行,这怎么办呢?所以我们用Promise的时候一般是包在一个函数中,需要它执行时再执行,比如:
<h1 onclick="asyncExe1()">点击我调用Promise,请在控制台查看结果</h1>
function asyncExe1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1--执行完成');
resolve('成功后传递的数据1');
}, 1000);
});
return p;
}此时 asyncExe1 返回的是一个实例对象,在【例2】和【例3】的代码中看到,resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject()方法的参数一般就是 Error 对象的实例,而 resolve() 方法 的参数可能是普通值,也可能是另一个 Promise 实例。
【例 4 】resolve() 方法 的参数 是一个实例对象
let p1 = new Promise((resolve,reject)=>{
resolve('p1传递的数据');
//reject('失败态的数据')
});
let p2 = new Promise((resolve,reject)=>{
resolve(p1)
})
p2.then((val)=>{
console.log('p2的结果是:' + val);//p2的结果是:p1传递的数据
},(err)=>{
console.log('p2的失败态--' + err);//p2的失败态--失败态的数据
})p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法调用的是 p1 这个对象,将p1作为了参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 fulfilled 或者 rejected,那么 p2 的回调函数将会立刻执行。也就是说:p2的then的状态取决于p1的状态。
Promise 的链式调用
【例 5】继续看个例子
function asyncExe1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1--执行完成');
resolve('成功后传递的数据1');
}, 2000);
});
return p;
}
function asyncExe2(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务2--执行完成');
resolve('成功后传递的数据2');
}, 2000);
});
return p;
}
function asyncExe3(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务3--执行完成');
resolve('成功后传递的数据3');
}, 2000);
});
return p;
}
asyncExe1().then((data)=>{
console.log(data);
return asyncExe2();
}).then((data)=>{
console.log(data);
return asyncExe3();
}).then((data)=>{
console.log(data);
})结果如图

上面提到过then()方法接收两个参数,在链式调用时它是怎样执行的呢?如上面这个【例5】的代码,在调用resolve函数时,then() 方法会一直调用成功回调函数,也就是一直会调用成功态时要执行的代码
【例 6】,当调用 reject 时, then()方法又是怎么执行的呢
let p = new Promise((resolve,reject)=>{
reject('不努力')
})
p.then((value)=>{
//成功时
console.log('恭喜1',value);
},(reason)=>{
//失败时
console.log('失败了1',reason);
}).then((value)=>{
//成功时
console.log('恭喜2',value);
},(reason)=>{
//失败时
console.log('失败了2',reason);
})结果如图:

promise链式调用的原理:返回最新的Promise实例
promise实例可以链式调用,连续调用then,then()返回的是一个新的promise实例。
1、上一次then的某个回调函数 返回值是一个promise实例,该实例的状态决定调用下一次的then的状态
- 该实例的状态是成功态,则直接调用下一次的成功态,
- 该实例的状态是失败态,则直接调用下一次的失败态
2、上一次then的某个回调函数是普通值,会执行下一次then的成功回调(上一次then的某个回调函数是失败态,会返回undefined,则下次的then会走成功态)
3、上一次then的某个回调函数抛异常,会执行下一次then的失败回调
Promise 的常用方法
Promise..catch(); 捕获异常错误
【 例 7 】是then的特殊应用,与then方法并行 ,捕获异常错误,和then的第二个参数一样,用来指定reject的回调。
new Promise((resolve,reject)=>{
//resolve('成功了');
reject('不努力');
}).then((val)=>{
console.log(val)
}).catch((err)=>{
console.log('catch 捕获错误 --' + err)
})
/*
运行结果:
catch 捕获错误 --不努力
*/Promise.all(); 并发,同时执行,
【 例 8 】all()方法可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。
function asyncExe1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1--执行完成');
resolve('成功后传递的数据1');
}, 2000);
});
return p;
}
function asyncExe2(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务2--执行完成');
resolve('成功后传递的数据2');
}, 2000);
});
return p;
}
function asyncExe3(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务3--执行完成');
resolve('成功后传递的数据3');
}, 2000);
});
return p;
}
Promise.all([asyncExe1(),asyncExe2(),asyncExe3()]).then((val)=>{
console.log(val);
})
/*
运行结果:
异步任务1--执行完成
异步任务2--执行完成
异步任务3--执行完成
[ '成功后传递的数据1', '成功后传递的数据2', '成功后传递的数据3' ]
*/all()方法接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作同时执行,等到它们都执行完后,all会把所有异步操作的结果放进一个数组中传给then,也就是上面代码中的 val 。all()方法可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。
- 只有当asyncExe1、asyncExe2、asyncExe3的状态都变成fulfilled,Promise对象的状态才会变成fulfilled,此时返回值组成一个数组,传递给Promise的回调函数。
- 如果 asyncExe1、asyncExe2、asyncExe3 之中的状态有一个变为 rejected,Promise对象的状态就变成rejected,会将该结果会传递给Promise的回调函数。
结果如图

Promise.race(); 竞争,谁先执行完成 ,谁就先执行回调
【 例 9 】,谁先回来 谁就决定了then的成功态或失败态,现在把 asyncExe2()的时间 改为 1 秒钟
function asyncExe1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1--执行完成');
resolve('成功后传递的数据1');
//reject('失败了')
}, 2000);
});
return p;
}
function asyncExe2(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务2--执行完成');
resolve('成功后传递的数据2');
//reject('任务二失败了')
}, 1000);
});
return p;
}
function asyncExe3(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务3--执行完成');
resolve('成功后传递的数据3');
}, 2000);
});
return p;
}
Promise.race([asyncExe1(),asyncExe2(),asyncExe3()]).then((val)=>{
console.log(val);
},(err)=>{
console.log('err--' + err)
})
/*
结果:
异步任务2--执行完成
成功后传递的数据2
异步任务1--执行完成
异步任务3--执行完成
*/
这三个异步操作同样是并行的。1秒后 asyncExe2() 已经执行完了,此时then里面的就执行了,结果如图

race干什么用呢,举一个大家经常遇到的情况:异步请求设置超时。 可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
【例 10 】race() ;异步请求设置超时 比如请求图片超时
function readImg(){
let p = new Promise((resolve,reject)=>{
let img = new Image()
img.onload = function(){
resolve(img);
}
img.src = 'xxxx';
})
return p
}
function raceTime(){
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('图片请求超时')
},5000)
})
return p2;
}
Promise.race([readImg(),raceTime()]).then((val)=>{
console.log(val)
},(err)=>{
console.log('出错了---' + err)
})readImg() 函数异步请求一张图片,地址写为"xxxxxx",所以肯定是无法请求到的。raceTime() 函数是一个延时5秒的异步操作。这两个都返回Promise对象,将这两个函数放进race()方法中,如果5秒之内图片请求成功了,那就进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,则执行reject的回调函数,报出“图片请求超时”的错误信息。
Promise能用的方法基本上就这些了。这是我对promise的一点点理解,总结出来。
好啦 这一篇先到这里。我的文章都是学习过程中的总结,如果发现错误,欢迎留言指出,我及时更正