总结一下Promise的基本使用和深入使用,为学习Promise源码打下坚实的基础
基本使用
Promise 是 JS 中进行异步编程的新解决方案(旧方案是单纯使用回调函数)
- 从语法上来说:
Promise是一个构造函数 - 从功能上来说:
Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
为什么要用 Promise
-
指定回调函数的方式更加灵活。没有
Promise之前必须在启动异步任务时同时指定回调函数。Promise是先启动异步任务,然后返回Promise对象,再给Promise对象绑定回调函数。在异步任务结束后也可以指定多个回调函数 -
支持链式调用, 可以解决回调地狱(回调函数中嵌套回调函数)问题。出现回调地狱,不便于阅读也不便于异常处
基本使用流程
基本编码流程
// 1) 创建 Promise 对象(pending 状态), 指定执行器函数
const p = new Promise((resolve, reject) => {
// 2) 在执行器函数中启动异步任务
setTimeout(() => {
const time = Date.now();
// 3) 根据结果做不同处理
// 3.1) 如果成功了, 调用 resolve(), 指定成功的 value, 变为 resolved 状态;
if (time % 2 === 1) {
resolve("成功的值 " + time);
} else {
// 3.2) 如果失败了, 调用 reject(), 指定失败的 reason, 变为rejected 状态
reject("失败的值" + time);
}
}, 2000);
});
// 4) 能 Promise 指定成功或失败的回调函数来获取成功的 vlaue 或失败的 reason
p.then(
(value) => {
// 成功的回调函数 onResolved, 得到成功的 vlaue
console.log("成功的 value: ", value);
},
(reason) => {
// 失败的回调函数 onRejected, 得到失败的 reason
console.log("失败的 reason: ", reason);
}
);
Promise 对象 状态 属性
Promise 的状态是实例对象中的一个属性 『PromiseState』
如果我们将 Promise 的实例打印出来:
他有三个值
'pending'未决定的'resolved'/'fullfilled'成功'rejected'失败 并且改变只存在两种可能。只能像下面这样变化:
'pending'变为'resolved''pending'变为'rejected'
说明:只有这2种改变方式,且一个 Promise 对象只能改变一次。
无论变为成功还是失败,都会有一个结果数据。成功的结果数据一般称为value,失败的结果数据一般称为reason
Promise 对象的 值 属性
这是实例对象中的另一个属性 『PromiseResult』
保存着异步任务『成功/失败』的结果,只有 resolve 和 reject 函数可以改变这个结果
Promise 构造函数
Promise 构造函数: Promise (excutor) {}
executor函数: 执行器(resolve, reject) => {}- 执行器中的
resolve函数: 内部定义成功时调用的函数value => {} - 执行器中的
reject函数: 内部定义失败时调用的函数reason => {}
说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行
let p = new Promise((resolve, reject) => {
// 同步调用
console.log(111);
});
console.log(222);
先输出111,后输出222
属性区分
用[[ ]]包裹起来的属性叫做ECMAScript官方规范中叫做内置属性.浏览器内部的这些属性不让用户自己操作,但可以暴露出来给我们看
公共属性
Promise.prototype.then 方法
Promise.prototype.then 方法: (onResolved, onRejected) => {}
onResolved函数: 成功的回调函数(value) => {}onRejected函数: 失败的回调函数(reason) => {}说明: 指定用于得到成功value的成功回调和用于得到失败reason的失败回调- 返回一个新的
Promise对象
Promise.prototype.catch 方法
Promise.prototype.catch 方法: (onRejected) => {}
onRejected 函数: 失败的回调函数 (reason) => {}
说明: 内部是对 then 的一个封装, then() 的语法糖, 相当于: then(undefined, onRejected)
let p = new Promise((resolve, reject) => {
//修改 Promise 对象的状态
reject('error');
});
//执行 catch 方法
p.catch(reason => {
console.log(reason);
});
Promise.resolve 方法
Promise.resolve 方法: (value) => {} 是 Promise 构造函数上的方法
value: 成功的数据或Promise对象
说明: 返回一个成功/失败的 Promise 对象
注意:
- 如果传入的参数为 非
Promise类型的对象, 则返回的结果为成功Promise对象 - 如果传入的参数为
Promise对象, 则参数的结果决定了resolve的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p2);
let p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})
tip:
如果有一个失败的Promise,而且我们还没有对失败的结果做处理,那么就会抛出错误
let p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}));//或者p2 = Promise.reject()
Promise.reject
Promise.reject 方法: (reason) => {} 也是构造函数上的方法
reason: 失败的原因 说明: 返回一个失败的Promise对象
注意:不管接受什么参数,返回的都是失败的 Promise 对象(即使是成功的 Promise 对象),返回的结果状态是 reject ,值就是传入的那个值,即返回的结果永远是失败的,而且传入什么值就返回什么值
let p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p3);
Promise.all 方法
Promise.all 方法: (Promises) => {}
Promises: 包含 n 个Promise的数组
说明: 返回一个新的 Promise , 只有所有的 Promise 都成功才成功,成功的结果就是所有Promise成功返回的结果组成的数组。只要有一个失败了就直接失败。
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
Promise.race 方法
Promise.race 方法: (Promises) => {}
race 本身的意思是赛跑的意思
Promises : 包含 n 个 Promise 的数组
说明: 返回一个新的 Promise, 第一个完成的 Promise 的结果状态(不管成功还是失败)就是最终的结果状态
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.race([p1, p2, p3]);
console.log(result);
响应p2,p2首先完成(记住不管是成功还是失败)
基本流程
两个基本使用的例子
封装一个读取文件函数
/**
* 封装一个函数 mineReadFile 读取文件内容
* 参数: path 文件路径
* 返回: Promise 对象
*/
function mineReadFile(path){
return new Promise((resolve, reject) => {
//读取文件
require('fs').readFile(path, (err, data) =>{
//判断
if(err) reject(err);
//成功
resolve(data);
});
});
}
mineReadFile('./resource/content.txt')
.then(value=>{
//输出文件内容
console.log(value.toString());
}, reason=>{
console.log(reason);
});
封装Ajax
/**
* 封装一个函数 sendAJAX 发送 GET AJAX 请求
* 参数 URL
* 返回结果 Promise 对象
*/
function sendAJAX(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断成功
if(xhr.status >= 200 && xhr.status < 300){
//成功的结果
resolve(xhr.response);
}else{
reject(xhr.status);
}
}
}
});
}
sendAJAX('https://api.apiopen.top/getJok')
.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
util.promisify
util.promisify传入一个遵循常见的错误优先的回调风格的函数(即以(err, value) => ...回调作为最后一个参数),并返回一个返回Promise的版本。 就是可以不用自己封装,直接返回Promise
/**
* util.promisify 方法
*/
//引入 util 模块
const util = require('util');
//引入 fs 模块
const fs = require('fs');
//返回一个新的函数
let mineReadFile = util.promisify(fs.readFile);
mineReadFile('./resource/content.txt').then(value=>{
console.log(value.toString());
});
深入使用(非常关键的细节问题)
如何修改Promise对象状态
pending->成功
pending => fulfilled (resolved)
let p = new Promise((resolve, reject) => {
//1. resolve 函数
// resolve('ok'); // pending => fulfilled (resolved)
});
pending ->失败
pending => rejected
两种方式:第一种 reject
let p = new Promise((resolve, reject) => {
//2. reject 函数
reject("error");// pending => rejected
});
第二种: throw 关键字抛出错误
let p = new Promise((resolve, reject) => {
throw '出问题了';
});
console.log(p)
let p = new Promise((resolve, reject) => {
throw new Error('出问题了');
});
console.log(p)
可指定多个回调
一个 Promise 指定多个成功/失败回调函数, 当 Promise 改变为对应状态时都会调用
let p = new Promise((resolve, reject) => {
// resolve('OK');
});
///指定回调 - 1
p.then(value => {
console.log(value);
});
//指定回调 - 2
p.then(value => {
alert(value);
});
如果没有改变状态,回调函数就不会执行
改变状态与指定回调顺序问题
是 resolve 改变状态方法先执行,还是 then 指定回调方法先执行?
都有可能, 正常情况下是先指定回调再改变状态(异步任务), 但也可以先改状态再指定回调(同步任务)
-
先改状态再指定回调(
resolve()或reject()先执行,then()后执行)直接调用
resolve()/reject(),当执行器中的任务是同步任务的时候,那就会先改变Promise的状态,在指定回调let p = new Promise((resolve, reject) => { resolve('OK'); }); p.then(value => { console.log(value); },reason=>{ })执行顺序是这样:先执行
resolve('OK'),再执行p.then(),再执行then()中的回调函数 -
先指定回调再改变状态(
then先执行,resolve或reject后执行)当执行器函数当中是异步任务,
then方法先执行,改变状态后执行,那也是可以的。改变状态之后,then中的回调函数才会执行let p = new Promise((resolve, reject) => { setTimeout(() => { resolve('OK'); }, 1000); }); p.then(value => { console.log(value); },reason=>{ })执行顺序是这样:先执行
p.then(),再执行resolve('OK'),再执行then()中的回调函数
即 Promise 执行器中支持同步任务也支持异步任务,在使用 Promise 的时候,后一种方式出现的比较多
then 方法的返回结果
看一个问题
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
let result = p.then(value => {
console.log(value);
},reason=>{
console.log(reason);
})
console.log(result)
Promise.then() 返回的新 Promise
那新的 Promise 实例的结果状态和值由什么决定?
新的 Promise 实例的结果状态和值,由 then()指定的回调函数执行的结果决定
回调函数执行的结果具体有以下几种情况.
注意:两个回调函数只会执行一个且不管执行哪个,都适合下面的情况
-
如果抛出异常 ,新
Promise状态变为rejected, 值reason为抛出的异常。let p = new Promise((resolve, reject) => { resolve('ok'); }); //执行 then 方法 let result = p.then(value => { //1. 抛出错误 throw '出了问题'; }, reason => { console.warn(reason); }); console.log(result) -
如果返回的是非
Promise的任意值, 新Promise状态变为resolved,值value为返回的值(函数不写return关键字,默认返回undefined)let p = new Promise((resolve, reject) => { resolve('ok'); }); //执行 then 方法 let result = p.then(value => { //2. 返回结果是非 Promise 类型的对象 }, reason => { }); console.log(result)let p = new Promise((resolve, reject) => { resolve('ok'); }); //执行 then 方法 let result = p.then(value => { //2. 返回结果是非 Promise 类型的对象 return 521; }, reason => { console.warn(reason); }); console.log(result)注意!!
let p = new Promise((resolve, reject) => { // 一个reject 的Promise实例 reject('fail'); }); let result = p.then(value => { return 'resolve返回了'; }, reason => { console.warn(reason); return 'reject返回了'; }); console.log('result',result) -
如果返回的是另一个新
Promise, 此Promise的结果就会成为then返回的那个新Promise的结果。状态和值都和新Promise的结果对应let p = new Promise((resolve, reject) => { resolve("ok"); }); //执行 then 方法 let result = p.then( (value) => { //3. 返回结果是 Promise 对象 return new Promise((resolve, reject) => { resolve('success'); //reject("error"); }); }, (reason) => { console.warn(reason); } ); console.log(result);resolve:
reject:
总结:
两个回调函数不管其中哪一个执行,then()返回的实例都遵循一下规则:
- 如果返回一个普通的值,或者返回一个
'resolved'状态的Promise实例,那么状态就是resolved,值就是返回的值或者resolve的值 - 如果抛出错误,或者
return返回'rejected'状态的Promise实例,那么值就是抛出的错误或者rejecte的值
链式调用串联多个任务
因为 then 返回的结果是新的 Promise 对象,所以我们可以用 then 串联起多个 Promise 任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(value)
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value);
})
一秒后打印出 'ok' , 'success'
这样我们可以把多个任务都写成 Promise 的形式,然后将任务串联起来。
注意:如果 then() 中的回调函数什么都不返回,最后会默认返回 undefined
let p = new Promise((resolve, reject) => {
resolve('ok')
});
p.then(value => {
console.log(value)
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})
第一个 then 返回的就是一个成功的 Promise ,其状态为 resolve ,值为 OK
第一个 then 返回的也是一个成功的 Promise ,其状态为 resolve ,值为上一个 then 的回调函数返回的结果 'undefined'
通过 then 的链式调用串连多个同步/异步任务
注意链式调用Promise的返回值变化
可以看下面的特殊的例子
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 10);
});
p.then(value => {
console.log('111value:',value);
return value
}).then(value => {
console.log('222value:',value);
throw '失败啦!';
}).then(value => {
console.log('333value:',value);
return '333resolve'
}, reason => {
console.warn('333reason:',reason);
return '333reject'
}).then((value) => {
console.log('444value', value)
},reason => {
console.warn('444reason',reason);
})
可以看到即使中间resolve了或者抛出错误,Promise链仍然不会中断。
并且可以看到第三个then执行的是第二个onReject回调函数,但是在第四个then仍然接收的是一个'resolve'状态的Promise。因为then()函数中的两个回调,只有在抛出错误和返回'reject'状态的Promise实例,then()函数返回的才是'reject'状态的Promise实例。这一点在上面then()的返回值 章节 里已经阐述。
因为catch仅仅是then的语法糖,所以catch也会返回一个Promise,等同于.then(undefined,(reason =>{}))
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 10);
});
p.then(value => {
console.log('111value:',value);
return value
}).then(value => {
console.log('222value:',value);
throw '失败啦!';
}).catch(reason => {
console.warn('333reason:',reason);
}).then((value) => {
console.log('444value', value)
},reason => {
console.warn('444reason',reason);
})
输出和上面一样
异常穿透
异常穿透:当使用 Promise 的 then 链式调用时, 可以在最后指定失败的回调。 前面任何操作出了异常, 都会传到最后失败的回调中处理。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Err');
}, 1000);
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
输出的是第一个Promise的值
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
throw '失败啦!';
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
捕获链中遇到的 reject
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Err');
}, 10);
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
}).then(value => {
console.log('444resolve');
},reason=>{
console.log('444reject');
})
catch完之后,返回的仍然是resolve状态的Promise实例,可以继续执行下面的链式调用
当没有onReject的回调的时候,就会一直往下传,不执行第一个回调,直到遇到onReject的回调为止。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('OK');
reject('fail')
}, 1000);
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
throw '失败啦!';
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
通过以上的理解我们知道,这个就是因为直接跳过了第二个then()内部的onResolve回调函数,所以不打印'失败啦',所以最后捕获的还是'fail'字符串
同样,一个resolve会跳过catch,相当于跳过then(undefined,()=>{}),直接到下一个then寻找onResolved回调函数。如下:
//创建一个新的 Promise ,且已决议
var p1 = Promise.resolve("calling next");
var p2 = p1.catch(function (reason) {
//这个方法永远不会调用
console.log("catch p1!");
console.log(reason);
});
p2.then(function (value) {
console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
console.log(value); /* calling next */
}, function (reason) {
console.log("next promise's onRejected");
console.log(reason);
});
总结:
catch只是相当于then()没有传第一个参数onResolve函数,只传了第二个onReject回调函数。在链式调用中,'reject'状态可以一直传递下去,直到遇到onReject回调函数(catch或者then第二个回调)才会被捕获,捕获后处理后,会返回'resolve'状态的Promise
中断 Promise 链
链式调用Promise的时候,无法从中间断开,会一直执行到最后。
如何中断?
当使用 Promise 的 then 链式调用时, 可以在中间中断, 不再调用后面的回调函数
中断办法: 在回调函数中返回一个 pendding 状态的 Promise 对象
当其返回 pendding 状态的 Promise 对象时,因为其状态没有改变,所以后面的 then 方法都不能执行
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
//有且只有一个方式
return new Promise(() => {});
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
文档: