> 简述:
## 什么是Promise?
- Promise是用来处理异步的;
- Promise就是承诺,对未来的承诺;
- 所谓的Promise(承诺),里面保存着未来才会结束的事件的结果;
- Promise是异步编程的一种解决方案; 比传统的解决方案(回调函数和事件)更合理更强大;
- Promise一个对象,从它可以获取异步操作的消息;
- Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了
### 两个特点
- 对象的状态不受外界影响, Promise对象代表一个异步操作;
#### 3种状态
1 pending 等待态
2 fulfilled 已成功
3 rejected 已失败
只有异步操作的结果,可以决定当前是哪一个状态,任何外部的操作都无法改变这个状态
- 状态一旦改变,就不会也不能再变,任何时候都可以得到这个结果;
Promise的状态,只能从pending变为fulfilled或rejected, fulfilled和rejected不能相互转换;
## 诞生的时代背景 ##
> 在JavaScript的世界中,所有代码都是单线程执行的; 由于这个"缺陷",以至于JavaScript的所有网络操作、浏览器事件等都必须异步执行; 异步意味着在未来的某个时刻得到结果; 在promise之前,我们写的ajax应用(前一个函数的执行结果作为下一个函数的参数),就面临着大量的回调函数的窘境;
## 为解决回调而生 ##
- Promise对象,可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数;
- Promise对象提供统一的接口,使得控制异步更加容易;
## 不是最perfect, 因为它还是基于回调 ##
- 无法取消Promise,一旦新建就会立即执行,无法中途取消;
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;
- 当处于pending时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
- 从本质上来说,Promise还是基于回调的
## 基础用法 ##
### 一、构造函数与then ###
- ES6规定,Promise对象是一个构造函数,用来生成Promise实例
- Promise接收一个函数(executor:执行者)作为参数,该函数有两个参数:
1 resolve(Function) -> 将Promise对象的状态从"未完成pending"变为"成功resolved",在异步操作成时调用,并将异步操作的结果,作为参数传递出去(供then方法的成功回调接收)
2 reject(Function) -> 将Promise对象的状态从"未完成pending"变为"rejected",在异步失败时调用,并将异步操作的错误或失败的原因,作为参数传递出去(供then方法的失败回调接收)
```
const promise = new Promise(function(resolve, reject){
if(/*异步操作成*/){
resolve(value); // 成功时调用它会将Promise的状态从"pending"变为"resolved"
}else{
reject(err); // 失败时调用它会将Promise的状态从"pending"变为"rejected"
}
});
```
- Promise实例生成后,可以用then方法分别指定resolved(参数是成功的结果)状态和rejected(参数是失败的原因)状态的回调函数; 即then方法接收两个回调函数作为参数:
1 第一个函数,是Promise对象的状态变为resolved时调用(接收成功的结果作为参数)
2 第二个函数,是Promise对象的状态变为rejected时调用[可选](接收失败的原因或错误作为参数)
```
promise.then((value)=>{
// value是成功的结果
}, (err)=>{
// 失败的原因
});
// 示例: demo/1.创建promise实例.js
```
- Promise的状态发生改变时就会触发then方法对应的不同状态的函数参数
### 二、Promise新建后会立即执行 ###
- Promise是同步的,promise.then方法是异步的
```
const promise = new Promise((resolve, reject)=>{
console.log('Promise');
resolve();
});
promise.then((value)=>{
console.log('Success')
});
console.log('Hi!');
// => Promie Hi! Success
// 上面代码,Promise新建后会立即执行,所以会先输出'Promise', 然后我们为then方法指定了回调函数,因为then方法是异步的,所以它会等待同步代码都执行完之后才执行,所以接下来输出的是'Hi!',当主栈中的同步代码执行完毕之后, 开始执行异步任务(then是个微任务), 因此promise状态改变后成功的回调输出'Success'
// 示例: demo/2.promise创建会立即执行
```
```
// 异步加载图片示例
function loadImageAsync(url){
return new Promise((resolve, reject) => {
let image = new Image();
image.onload = function(){
resolve(image); // 如果图片加载成则调用该方法, 将promise的状态改为resolved
};
image.onerror = function(){
reject('创建失败'); // 如果图片加载失败, 则调用该方法, 将promise的状态改为rejected
};
image.src = url;
});
}
loadImageAsync('./timg2.jpg').then((value)=>{
// 如果状态变为成功态,则将图片添加到页面
console.log(value);
document.querySelector('body').appendChild(value);
}, (err)=>{
// 状态变为失败态,则将提示错误
console.log('Error:', err);
});
// 示例: demo/3.异步加载图片
```
```
// Promise对象实现Ajax示例
let getJSON = function(){
return new Promise((resolve, reject)=>{
$.getJSON('./test.json').then((value)=>{
console.log(value);
resolve(value)
}, (err)=>{
// console.log('失败回调:',err)
reject(err);
});
});
};
getJSON().then((data)=>{
console.log(data)
},(err)=>{
console.log('My-Error:', err);
});
// 如果调用resolve函数和reject函数时带有参数,那么它们的参数被被传递给回调函数(then的成功回调和失败回调)
// 示例: demo/4.Promise对象实现Ajax
```
- Promise的回调函数中抛出错误或异常,则直接调用then的失败态(reject)回调函数
```
new Promise((resolve,reject)=>{
throw new Error('错误');
}).then((data)=>{
console.log(data)
},(err) => {
console.log('Err ', err); //Err Error: 错误....
return err;
})
```
### 三、resolve函数返回Promise实例 ###
> resolve函数的参数除了正常的值以外,还能是另一个Promise实例
```
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作
注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行
```
```
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
//上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数
// 一言以蔽之: "传递的promise都要执行,直到执行后的结果是普通值(非promise)或抛出异常, 再传递到下一个then中, 执行对应的回调"
```
### 四、调用resolve与reject并不会终结Promise的参数函数(executor)执行 ###
```
new Promise((resolve,reject)=>{
resolve('success');
console.log('Hello')
}).then(data=>{
console.log(data)
});
// => 'Hello' 'success'
// 一般来说, 调用resolve和reject以后,Promise的使命就完成了,后续的操作应该放到then方法里面,而不应该直接写在resolve和reject的后面; 所以,最好在它们前面加上return语句;
```
```
new Promise((resolve,reject)=>{
return resolve('success');
console.log('Hello')
}).then(data=>{
console.log(data)
});
// 此时只输出success
```
### 五、Promise.prototype.then() ###
- Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的;
- then方法的作用是为Promise实例添加状态改变时的回调函数,它的第一个参数是resolve状态的回调函数,第二个参数是rejected状态的回调函数;
- then方法返回的是一个新的Promise实例(注意不是原来那个Promise实例);
- 链式写法,即then方法后面再调用另一个then方法
1、 then链式调用, 第一个then回调的返回结果会作为第二个then回调的参数
```
new Promise((resolve,reject)=>{
return resolve('success'); // 将状态改变为成功态
}).then((data)=>{
console.log(data) // success
return '成功' + data;
},(err) => {
console.log('Err', err);
return err;
}).then((data) => {
console.log(data) // 成功success
}, (err) =>{
console.log("Fail", err)
});
// 上面代码使用then方法, 依次执行两个then的回调函数. 第一个回调完成后,会将返回结果作为参数,传入到第二个回调函数;
```
2、 then的reject回调函数中返回错误,会继续传递到下一个then的成功(resolve)回调**`切记`**
```
new Promise((resolve,reject)=>{
return reject('fail'); // 执行失败回调
}).then((data)=>{
console.log(data);
return '成功';
},(err) => {
console.log('Err', err); // Err fail
return err;
}).then((data) => {
console.log('hello',data) // hello fail
}, (err) =>{
console.log("Fail", err)
});
```
3、then()会多层传递
```
new Promise((resolve,reject)=>{
resolve('ABC');
}).then().then().then((data)=>{
console.log(data)
},(err)=>{
console.log(err)
});
// 输出 "ABC"
```
### 六、Promise.prototype.catch() ###
- Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数
```
new Promise((resolve,reject)=>{
throw new Error('错误');
}).catch((err)=>{
console.log('Error:', err); // Error: Error: 错误
});
等同于
new Promise((resolve,reject)=>{
throw new Error('错误');
}).then(null, (err)=>{
console.log('Error:', err); // Error: Error: 错误
});
```
- 在Promise的回调函数中直接抛出错误,此时状态变为rejected, 错误会被catch捕获
```
new Promise((resolve,reject)=>{
throw new Error('错误');
}).catch((err)=>{
console.log('Error:', err); // Error: Error: 错误
});
```
- then方法指定回调函数, 在运行中抛出错误, 也会被catch捕获
```
new Promise((resolve,reject)=>{
resolve('ABC'); // 将状态改变为成功态
}).then((data)=>{
console.log(test) // 在then的成功回调中执行,输出一个未定义的变量,此时会抛出错误
},(err) => {
console.log('Err ', err);
}).catch((err)=>{
console.log('Catch ', err); // 在这里被捕获 => Catch ReferenceError: test is not defined ....
});
```
- 如果Promise的状态已经变成resolved, 再抛出错误是无效的
```
new Promise((resolve,reject)=>{
resolve('ABC');
throw new Error('错误');
}).then((data)=>{
console.log(data) // 输出ABC
},(err) => {
console.log('Err ', err);
}).catch((err)=>{
console.log('Catch ', err);
});
// 上面代码执行输出了"ABC",说明在状态改变后抛出错误,对结果无影响
// 上面代码中,Promise在resolve语句后面抛出错误,没有被捕获,相当于没有抛出错误;因为Promise的状态一旦改变,就永远保持该状态,不会再变了;
```
- reject的作用等同于抛出错误
```
new Promise((resolve,reject)=>{
reject('ABC');
}).catch((err)=>{
console.log('Catch ', err);
});
// 输出: "Catch ABC"
等同于
new Promise((resolve,reject)=>{
try{
console.log('a')
throw new Error('test');
}catch(e){
console.log('b')
reject(e)
}
}).catch((err)=>{
console.log('Catch ', err);
});
// => a b Catch Error: test
```
### Promise.all() ###
- Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例
- Promise.all方法接受一个数组作为参数(数组中的每一项都是一个promise实例),只有所有的实例状态都变为fulfilled, promise实例的状态才会变成fulfilled, 此时数组中promise实例的返回值会组成一个数组,传递给该promise实例的回调函数, 返回值在数组中的顺序与promise数组中promise实例的顺序一致
```
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(100);
}, 2000);
});
let p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('Hello');
}, 3000);
});
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(true);
}, 2000);
});
Promise.all([p1, p2, p3]).then((data)=>{
console.log(data); // 等数组中的promise实例都执行完成后,才将状态改变为fulfilled => [ 100, 'Hello', true ]
}, (err)=>{
console.log(err)
});
// => [ 100, 'Hello', true ]
```
- 如果其中有一个promise实例抛出错误,就会走到Promise.all().catch()方法
```
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(100);
}, 2000);
});
let p2 = new Promise((resolve,reject)=>{
throw Error('错误啦');
});
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(true);
}, 2000);
});
Promise.all([p1, p2, p3]).then((data)=>{
console.log(data)
}).catch(err=>{
console.log('Promise.all Error:', err)
});
// => Promise.all Error: Error: 错误啦....
// 在p2执行时抛出了异常,那么整个Promise.all()执行状态被改变为rejected, 错误被catch捕获
```
- 如果作为参数的Promise实例,自己定义了catch方法, 那么一旦被rejected,并不会触发Promise.all()的catch方法
```
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(100);
}, 2000);
});
let p2 = new Promise((resolve,reject)=>{
throw Error('错误啦');
}).catch(err=>{
console.log('p2.Error', err); // p2.Error Error: 错误啦
});
Promise.all([p1, p2]).then((data)=>{
console.log(data); // [ 100, undefined ]
}).catch(err=>{
console.log('Promise.all Error:', err)
});
// 上例中,p1在2秒后状态改变为resolve; p2执行时抛出错误,此时p2的状态改变为rejected, 因为它有自己的catch,在执行完catch方法后,状态也变成了resolve, 因此会调用then的指定的回调, 而不会调用catch方法指定的回调函数; 所以,最终打印出: [ 100, undefined ]
```
### Promise.race() ###
- Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例
- 同上面all()方法相比,只要有一个实例率先改变状态,整个promise实例的状态就跟随改变; 与此同时,第一个改变的promise实例(数组中的)的值,被传递给Promise的回调函数
```
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(100);
}, 2000);
});
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(true);
}, 2500);
});
Promise.race([p1, p3]).then((data)=>{
console.log(data) // 100
}).catch(err=>{
console.log('Promise.all Error:', err)
});
```
- 如果promise数组中有一个普通值,会立马返回
```
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(100);
}, 2000);
});
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(true);
}, 2500);
});
Promise.race([p1, p3, 10086]).then((data)=>{
console.log(data) // 10086
}).catch(err=>{
console.log('Promise.all Error:', err)
});
// => 10086; 因为我们最终要使用的是普通值或普通对象,而非promsie,所有的实例最终的目的都是要返回普通值, 因此遇到普通值会立马返回
```
### Promise.resolve() ###
- Promise.resolve()可用于将现有对象转换为Promise对象
- `Promise.resolve('foo')`等价于`new Promise(resolve=>resolve('foo'))`
- Promise.resolve方法的参数分成四种情况:
1、参数是一个 Promise 实例
- 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例
2、参数是一个thenable对象(thenable对象指的是具有then方法的对象)
- Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法
```
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
// thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42
```
3、参数不是具有then方法的对象,或根本就不是对象
- 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise对象,状态为resolved
```
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
// 上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数
```
4、不带有任何参数
- Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的Promise对象,所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法
### Promise.reject() ###
- Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected
```
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
//上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行
```
- Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数.这一点与Promise.resolve方法不一致
```
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
// 上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的"出错了"这个字符串,而是thenable对象
```
### 参考文档 ###
[ECMAScript 6 入门 - Promise对象](http://es6.ruanyifeng.com/#docs/promise)