一、Promise 概述
Promise 对象是 ES6 提供的原生的内置对象
Promise 是异步编程的一种解决方案(异步代码同步化),比传统的解决方案——回调函数和事件——更合理和更强大
- Promise 对象代表一个异步操作, 其不受外界影响,有三种状态:
- Pending(进行中、未完成的)
- Resolved(已完成,又称 Fulfilled)
- Rejected(已失败)
二、Promise 对象的 优缺点
1. 优点
- 异步编程解决方案,避免了回调地狱(Callback Hell)问题
// 回调地狱firstAsync(function(data){ //处理得到的 data 数据 //.... secondAsync(function(data2){ //处理得到的 data2 数据 //.... thirdAsync(function(data3){ //处理得到的 data3 数据 //.... }); });});
// 使用 Promise 解决方案firstAsync().then(function(data){ //处理得到的 data 数据 //.... return secondAsync();}).then(function(data2){ //处理得到的 data2 数据 //.... return thirdAsync();}).then(function(data3){ //处理得到的 data3 数据 //....});
2. 缺点
- Promise 对象,一旦新建它就会立即执行,无法中途取消
let Promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve();});Promise.then(function() { console.log('resolved.');});console.log('Hi!');// Promise// Hi!// resolved
- Promise 状态一旦改变,就不会再变
const Promise = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test');});Promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) });// 只输出 ok
- 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
三、Promise 的基本用法
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例
语法:
Promise 构造函数:接收一个函数参数,函数参数 中有两个参数
resolve
、reject
参数
resolve
、reject
都是函数(由 JavaScript 引擎提供,不用自己部署),在 异步操作成功时,手动执行 函数resolve
;在 异步操作失败时,手动执行 函数reject
Promise实例生成以后,可以用
then
、catch
方法分别指定 成功状态、失败状态的回调函数
const promise = new Promise(function(resolve, reject) { // 异步操作 code // 手动执行 resolve / reject 函数 if (/* 异步操作成功 */){ resolve(value); } else { reject(error); }});promise .then((value) => {}) .catch((error) => {});
异步编程解决方案: 将异步任务同步操作(一般情况下 Promise 对象 集结合 then、catch 使用)
异步任务:在 创建 Promise 对象中 执行( Promise 对象中的代码 会立即执行 )
需要在异步任务之后执行的代码:放到 then、catch 中
四、Promise 原型上的 方法
1. then 方法
作用: 为 Promise 实例添加 状态改变(成功、失败) 时的回调函数
- 语法:
Promise.then(resolve, reject)
- 参数:
参数
resolve
:异步操作成功时的回调函数参数
reject
:异步操作失败时的回调函数(可省略)
- 返回值: 一个新的Promise实例(不是原来那个Promise实例),可采用链式写法
- 参数:
getJSON("/posts.json").then(function(json) { return json.post;}).then(function(post) { // ...});// 如上:第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数
- 返回值 有可能是 含有异步操作的 Promise 对象, 这时之后的
then
方法中的函数,就会等待该Promise
对象的状态发生变化,才会被调用
const getPromise = () => { const Promise = new Promise(function (resolve, reject) {}); return Promise;}getPromise().then( param => getPromise(param)).then( comments => console.log("resolved: ", comments), err => console.log("rejected: ", err));
2. catch 方法
作用: 为 Promise 实例添加 状态变为
reject
时 的回调函数语法:
Promise.catch(reject)
等同于:
Promise.then(null, reject)
// 示例如下(Promise抛出一个错误,就被catch方法指定的回调函数捕获)const Promise = new Promise(function(resolve, reject) { throw new Error('test');});Promise.catch(function(error) { console.log(error);});// Error: test
- Promise 对象的错误具有“冒泡”性质: 会一直向后传递,直到被捕获到为止
const getPromise = () => { const Promise = new Promise(function (resolve, reject) {}); return Promise;}getPromise().then(function(post) { return getJSON(post.commentURL);}).then(function(comments) { // some code}).catch(function(error) { // 处理前面三个Promise产生的错误:无论是 getJSON产生错误,还是 then 产生的错误,都会由 catch 捕捉到});
- 最佳实践: 一般来说,不要在then方法里面定义
reject
状态的回调函数
// badpromise .then(function(data) { // success }, function(err) { // error });// goodpromise .then(function(data) { // success }) .catch(function(err) { // error });
3. finally 方法
作用: 为 Promise 实例添加 回调函数(不管状态是
resolve
、reject
),该回调函数都执行语法:
promise.finally(() => {})
不接受任何参数举例应用:服务器使用 Promise 处理请求,然后使用
finally
方法关掉服务器
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
4. then
、catch
、finally
回调的执行顺序
以下原则 适用于:
new Promise(...)
、Promise.resolve(...)
、Promise.reject(...)
回调函数如果是 同步函数: 会在本轮事件循环的末尾执行(下一轮循环之前)
回调函数如果是 异步函数: 会在本轮事件循环的末尾 将 该异步函数放入到事件循环队列中,等待时间为 0s (也就是在下一轮循环的最后执行)
示例如下:
// 回调函数 是 同步函数setTimeout(function () { console.log(3);}, 0);const promise = new Promise(function (resolve, reject) { console.log(6); resolve();});setTimeout(function () { console.log(4);}, 0);promise.then(() => { console.log(2);});setTimeout(function () { console.log(5);}, 0);console.log(1);// 6 1 2 3 4 5
// 回调函数 是 异步函数setTimeout(function () { console.log(3);}, 0);const promise = new Promise(function (resolve, reject) { console.log(6); resolve();});setTimeout(function () { console.log(4);}, 0);promise.then(() => { setTimeout(() => { console.log(2); }, 0);});setTimeout(function () { console.log(5);}, 0);console.log(1);// 6 1 3 4 5 2
5. 回顾:JS 的异常捕获
作用: 对于可能会出现错误的代码,使用异常捕获方式,可以使 JS 运行到错误代码处,不终止代码执行 且 抛出异常错误
- 三种最佳实践:
try...catch
try...finally
try...catch...finally
- 详解:
try 语句: 包含 可能出现错误的代码
catch 语句: 包含 捕捉到错误,执行的代码
finally 语句: 包含 无论有没有错误,都要执行的代码
示例:
try { console.log(11); // 可能会发生错误的代码}catch (e) { throw e; // 抛出错误异常,且 catch 代码块中 throw 之后的代码 不会执行}finally { }
五、多个异步 都 执行完成后,再执行回调:Promise.all()
作用: 将多个异步函数 包装成一个 内部并行执行的 Promise 实例
关键: 包装后的 Promise 实例,内部函数 并行执行;减少了执行时间
- 语法:
const promise = Promise.all([p1, p2, p3])
p1、p2、p3都是 Promise 实例;如果不是Promise 实例,就会先调用
Promise.resolve
方法,将参数转为 Promise 实例参数可以不是数组,但 必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
使用场景: 多个异步 都 执行完成后,再执行回调
Promise .all([runAsync1(), runAsync2(), runAsync3()]) .then((results) => { console.log(results); }); // 如上:三个异步操作 会 并行执行,等到它们都执行完后,才会执行 then 里面的代码;// 三个异步操作的结果,组成数组 传给了 then; 也就是 输出上述 result 为 [异步1数据,异步2数据,异步3数据]
- 包装后的 Promise 的状态(区别所在)
只有p1、p2、p3的状态都变成
fulfilled
,p的状态才会变成fulfilled
;此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数只要p1、p2、p3之中有一个被
rejected
,p的状态就变成rejected;此时第一个被reject
的实例的返回值,会传递给p的回调函数
- Promise 的状态 决定执行
then
回调,还是执行catch
回调
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result) .catch(e => e);const p2 = new Promise((resolve, reject) => { throw new Error('报错了'); }) .then(result => result);Promise.all([p1, p2]) .then((result) => { console.log('then'); console.log(result) }) .catch((e) => { console.log('error') console.log(e); });// 'error', Error: 报错了
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result) .catch(e => e);const p2 = new Promise((resolve, reject) => { throw new Error('报错了'); }) .then(result => result) .catch(e => e);Promise.all([p1, p2]) .then((result) => { console.log('then'); console.log(result) }) .catch((e) => { console.log('error') console.log(e); });// 'then', ["hello", Error: 报错了]// 解释:因为 p2 有自己的 catch, 错误才 p2 处被拦截
六、多个异步 中 有一个完成,就执行回调:Promise.race()
作用: 将多个异步函数 包装成一个 内部并行执行的 Promise 实例
关键: 包装后的 Promise 实例,内部函数 并行执行;减少了执行时间
- 语法:
const promise = Promise.race([p1, p2, p3])
p1、p2、p3都是 Promise 实例;如果不是Promise 实例,(如果是函数)就会先调用下面讲到的
Promise.resolve
方法,将参数转为 Promise 实例参数可以不是数组,但 必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
使用场景: 多个异步 中 有一个完成,就执行回调
Promise .race([runAsync1(), runAsync2(), runAsync3()]) .then((results) => { console.log(results); }); // 如上:三个异步函数会并行执行,只要有一个执行完,就会执行 then;// 率先执行完的异步函数的结果 作为参数 传递给 then 中的回调函数
// 请求超时的 democonst p1 = myAjax('/resource-that-may-take-a-while');const p2 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) });Promise.race([p1, p2]) .then(response => console.log(response)) .catch(error => console.log(error));
- 包装后的 Promise 的状态(区别所在)
- p1、p2、p3 中 率先 改变状态的 Promise 的状态值,直接影响 包装后的Promise 的状态;
- 那个率先改变的 Promise 实例的返回值,就传递给p的回调函数
六、快速生成 Promise 对象 的方法
1. Promise.resolve()
作用: 将现有对象转为 Promise 对象(状态为
resolve
)等价于:
Promise.resolve('foo');// 等价于new Promise((resolve, reject) => { resolve('foo');});
- 参数的几种形式:
参数是 具有then方法的对象: 转为 Promise 对象,并立即执行对象的 then 方法 【重点区分】
参数是 Promise 实例: 那么Promise.resolve将不做任何修改、原封不动地返回这个实例
不带有任何参数: 最快速生成 Promise 对象
// 参数是 具有then方法的对象let thenable = { then: function (resolve, reject) { console.log('then'); resolve(42); }};Promise.resolve(thenable); // 输出 then
// 不带参数,快速生成 Promise 对象Promise.resolve();
2. Promise.reject()
作用: 返回一个新的 Promise 实例,该实例的状态为
rejected
等价于:
Promise.reject('出错了');// 等价于new Promise((resolve, reject) => { reject('报错了');});
- 参数: 无论参数是什么都会原封不动的作为 错误理由 【重点区分】
七、Promise 的应用
- Promise 实现 异步加载图片
// Promise 实现 单张图片 异步加载(预加载)function loadImageAsync(url) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = function () { resolve(image); }; image.onerror = function () { reject(new Error('Could not load image at ' + url)); }; image.src = url; });}loadImageAsync('./1png') .then((img) => { console.log(img); // 图片标签 })
// Promise 实现 多张图片 异步加载(预加载)function loadImageAsync(url) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = function () { resolve(image); }; image.onerror = function () { reject(new Error('Could not load image at ' + url)); }; image.src = url; });}function getImages(source) { const imgs = []; source.forEach(url => { loadImageAsync(url) .then((img) => { imgs.push(img); }) }); return imgs;}console.log(getImages(['1.png', '2.png']));
// ES5 实现图片异步加载function getAsyncLoadImages(source) { const imgs = []; source.forEach(function (url, i) { imgs[i] = new Image(); imgs[i].onload = function () { // console.log(imgs[i]); } imgs[i].onerror = function () { reject(new Error('Could not load image at ' + url)); }; imgs[i].src = url; }) return imgs;}const result = getAsyncLoadImages(['1.png', '2.png']);console.log(result);