Promise 是ES6引入的异步编程的新解决方案。

语法上Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

Promise 构造函数: Promise(excutor){}

Promise.prototype.then 方法

Promise.prototype.catch 方法

Promise

实例化 Promise 对象;

Promise 回调函数的两个形参(默认规范): resolve 和 reject ,也是两个函数;调用这两形参函数分别代表构造出的新对象的两种状态:成功 和 失败;

const p = new Promise(function(resolve,reject){
setTimeout(function(){
let data = "成功获取用户数据";
resolve(data); // 调用 resolve 函数,在then 中对应 value,data 作为参数传给了 value
},1000);
});
// 调用 promise 里的 then 方法,这个方法有两个回调函数作为参数,分别对应 resovle 和 reject 两种状态
// 成功的形参叫 value ,失败的形参叫 reason,也是默认规范
p.then(function(value){ // Promise 中调用了 resolve ,then 中对应的调用 value
console.log(value); // 成功获取用户数据 会显示 resolve 返回的数据;
},function(reason){

});

Promise实例化的 p 对象有三种状态:初始化,成功 和 失败;

当调用 resolve 函数时,即标识 p 对象的状态为成功时,将在 then 方法里调用第一个参数函数,即 value 对应的函数;

当调用 reject 函数时,即标识 p 对象的状态为失败时,那 then 方法将执行第二个回调函数,即 reason 对应的函数;

上面实例中,p 对象调用了 resolve 并返回了 data ,那么 then 方法中会执行第一个函数,并把 data 作为参数,传给了 value ,最后输出;

如果异步函数 setTimeout 中调用了 reject 函数呢? 

const p = new Promise(function (resolve, reject) {
setTimeout(function () {
let err = "数据读取失败";
reject(err); // 调用了 reject 函数,相当于设定 p 对象的状态为失败,then 方法会执行第二个函数
}, 1000);
});
p.then(function (value) {
console.log(value);
}, function (reason) { // 因调用了 reject 函数,所以 then 方法执行第二个函数
console.log(reason); // 数据读取失败
});

通过这种方式 ,把异步任务封装在 Promise 的对象 p 里面; 通过 resolve 和 reject 函数来改变 p 对象的状态,从而影响 then 方法里的回调函数;即“成功”就调第一个函数,“失败”就调第二个函数;


官方说法,一个 Promise 必然处于以下几种状态之一:

Javascript(笔记38) - ES6特性 - Promise(基础)_构造函数

待定(pending):初始状态,既没有被兑现,也没有被拒绝。

已兑现(fulfilled):意味着操作成功完成。 

已拒绝(rejected):意味着操作失败。


可在视频教程中看到的成功状态可能是:resolved ,而不是 fulfilled ,暂时理解意思相同;



Promise封装读取文件

需要 Node 环境,先引入 fs 模块,再调用方法读取文件;

const fs = require('fs');
fs.readFile('./res/为学.md',(err,data)=>{
if(err) throw err;
console.log(data.toString());
});

通过 Promise 来封装:

const fs = require('fs');
const p = new Promise(function (resolve, reject) {
// 读取文件是异步操作
fs.readFile('./res/为学.md', (err, data) => {
// 如果失败,改变为失败状态 reject,可设置失败值为 err
if (err) reject(err);
// 如果成功,改变状态为成功
resolve(data);
});
});
// 不是一定要用 value 和 reason 作为形参,只是约定俗成的命名
p.then(function (value) {
console.log(value.toString());
}, function (reason) {
console.log("读取失败");
});

Javascript(笔记38) - ES6特性 - Promise(基础)_回调函数_02

明明几行代码就能做好的事情,还用费这么大劲? 

因为单个任务看不出来,要是有多个异步任务,尤其是有先后顺序的异步任务时,使这种方法会不断缩进,产生回调地狱问题,使用 Promise 就不会出这个问题。


Promise封装AJAX

创建原生的AJAX

//1, 创建对象
const xhr = new XMLHttpRequest();
// 2, 初始化
xhr.open("get", "https://api.apiopen.top/api/sentences");
// 3, 发送
xhr.send();
// 4, 事件绑定,处理响应结果
xhr.onreadystatechange = function () {
// 判断
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 表示成功
console.log(xhr.response);
} else {
// 表示失败
console.log(xhr.status);
}
}
}

用 Promise 封装 AJAX

const p = new Promise(function(resolve,reject){
const xhr = new XMLHttpRequest();
xhr.open('get','https://api.apiopen.top/api/sentences');
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >=200 && xhr.status <300){
resolve(xhr.response);
}else{
reject("xhr.status :" + xhr.status);
}
}
}
});
// 指定回调
p.then(function(value){
console.log(value);
},function(reason){
console.log(reason);
});

Promise的then方法

先创建 promise 对象,再调用 then 方法: 

const p = new Promise(function(resolve,reject){
setTimeout(()=>{
resolve('获取用户数据');
},1000);
});
p.then((value)=>{
console.log(value); // 获取用户数据
},(reason)=>{
console.warn(reason);
});

若状态为成功,则调用 then 方法的第一个函数;

若状态为失败,则调用 then 方法的第二个函数;

const p = new Promise(function(resolve,reject){
setTimeout(function(){
// resolve('获取用户数据');
reject('出错了');
},1000);
});
p.then((value)=>{
console.log(value);
},(reason)=>{
console.warn(reason); // 出错了
});

改为 reject 后,状态为失败,控制台正常输出数据,没有问题;

then() 方法的返回结果,也是一个 promise 对象;状态由回调函数的执行结果决定;

把返回结果赋给 result ;

const p = new Promise(function(resolve,reject){
setTimeout(function(){
resolve('获取用户数据');
},1000);
});
// 把 then 的返回结果赋值给 result
const result = p.then((value)=>{
console.log(value); // 获取用户数据,这里是正常输出
},(reason)=>{
console.warn(reason);
});
console.log(result);

Javascript(笔记38) - ES6特性 - Promise(基础)_构造函数_03

result 也是个 promise 对象;也有状态,当前状态是: fulfilled ,也就是成功;当前无返回值,也就是 undefined; 

情况1:如果回调函数返回的结果是 “非Promise” 类型的值,那么 result 状态为成功,返回值就是对象的值;

const p = new Promise(function(resolve,reject){
setTimeout(function(){
resolve('获取用户数据');
},1000);
});

const result = p.then((value)=>{
console.log(value); // 获取用户数据
return "abc"; // 返回 abc
},(reason)=>{
console.warn(reason);
});
console.log(result);

Javascript(笔记38) - ES6特性 - Promise(基础)_ES6_04

"abc" 是字符串结果,是“非Promise”值,那么状态为成功 "fulfilled",result 的值就是 "abc";

如果不写 return ,即返回 undefined ,也是 非Promise 值,所以状态也为 "fulfilled";

情况2:如果回调函数返回的结果 是Promise对象,那内部返回的状态即为 result 的状态;

const p = new Promise(function(resolve,reject){
setTimeout(function(){
resolve('获取用户数据');
},1000);
});

const result = p.then((value)=>{
console.log(value);
return new Promise((resolve,reject)=>{ // 返回了 promise 对象
resolve('OK'); // 状态为 成功
});
},(reason)=>{
console.warn(reason);
});
console.log(result);

Javascript(笔记38) - ES6特性 - Promise(基础)_构造函数_05

then 方法 里返回了一个 Promise 对象,那么 result 的状态就由这个对象的状态决定,当前是成功状态,所以 result 的状态也为成功,值即为返回的值:"OK";

如果返回的是失败呢?

// resolve('OK'); 
reject('error'); // 改为失败

Javascript(笔记38) - ES6特性 - Promise(基础)_ES6_06

情况3:抛出错误,那状态也是 失败;

// resolve('OK'); 
// reject('error');
throw new Error('出错了');

Javascript(笔记38) - ES6特性 - Promise(基础)_构造函数_07

总结:既然 then() 方法返回的也是 Promise对象,也有对象的状态,所以 then 方法就可以链式调用


then链式调用 

根据then的特点,可以使用这样的链式调用:

p.then((value)=>{},(reason)=>{}).then((value)=>{},(reason)=>{}).then(...)

也可以不用都写 reason:

p.then((value)=>{}).then((value)=>{});

可以在then方法里再写异步任务,一个个链起来,从而避免了回调地狱问题;


Promise实践练习

读取多个文件:

const fs = require('fs');
fs.readFile('./res/笔记1.md',(err,data1)=>{
fs.readFile('./res/笔记2.md',(err,data2)=>{
fs.readFile('./res/笔记3.md',(err,data3)=>{
let result = data1 + "\r\n" + data2 + "\r\n" + data3;
console.log(result);
});
});
});

平时可能不这么写,但如有层层递进的需求时,就会形成这种回调地狱;

换成 promise 的形式:

const fs = require('fs');
const p = new Promise((fulfill,reject)=>{
fs.readFile('./res/笔记1.md',(err,data)=>{
fulfill(data);
});
});
p.then((value)=>{
return new Promise((fulfill,reject)=>{
fs.readFile('./res/笔记2.md',(err,data)=>{
data = [value,data];
fulfill(data);
});
});
}).then((value)=>{
return new Promise((fulfill,reject)=>{
fs.readFile('./res/笔记3.md',(err,data)=>{
// 压入
value.push(data);
fulfill(value);
});
});
}).then((value)=>{
console.log(value.join('\r\n'));
});

通过then方法,把多个异步任务串联起来,看起来有些啰嗦,没有回调地狱的问题;


Promise对象的catch方法

这个方法用来指定 Promise对象失败的回调;

const p = new Promise((fulfill,reject)=>{
setTimeout(()=>{
// 设置 p 对象的状态为失败,并设置失败的值
reject("出错了");
},1000);
});

p.then(value=>{},reason=>{
console.error(reason);
});

then 有两个回调函数,第一个处理成功的状态;第二个处理失败的状态;也可以只写第一个;

所以,专门处理失败的回调,就可以使用 catch 方法了;

p.catch(reason=>{
consol.error(reason);
});

​catch 方法相当于 then 的语法糖​​,语法糖是用简练的形式表达较复杂的含义,相当于汉语中的“成语”;

从用法上:

p.then(null,reason=>{});

p.catch(reason=>{});

这两个等价;