总结一下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);
});
文档: