Promise对象的详解与实现
参考资料:《ES6标准入门第3版》
目录
2.5 Promise.resolve() 和 Promise.reject() 把现有对象转为Promise对象
2.6 Promise.all() 和 Promise.race()
一 、Promise对象详解
1. 什么是Promise?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件更合理和更强大,解决了传统解决方案处理异步编程出现回调地狱的情况。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
1.1 Promise对象的三种状态
Promise对象代表着一个异步操作,它有三种状态:pending(异步操作进行中)、fulfilled(异步操作已完成)、rejected(异步操作已经失败)
1.2 Promise对象的三个特点
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,至始至终会一直保持这个结果。
(3)Promise
也有一些缺点。例如,无法取消Promise
,一旦新建Promise对象它就会立即执行传入的函数,无法中途取消。
注意,为了方便理解(与 resolve 函数对应),统一使用 resolved 指代 fullfilled
状态。
2. 基本用法
ES6 规定,Promise
对象是一个构造函数,用来生成Promise
实例。
下面代码创造了一个Promise
实例。
注:文件读写是一个异步操作。
const fs = require('fs')
var promise = new Promise(function (resolve, reject) {
fs.readFile('./1.txt', 'utf-8', (err, data)=>{
if(err)
// 异步操作失败:文件读写失败
reject(err)
else
// 异步操作成功:文件读写成功
resolve(data)
})
})
2.1 新建Promise传入的参数
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去,参数也可不传。
reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去,参数也可不传。
2.2 then方法的作用
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数,Promise 实例对象的 .then 方法中接收两个参数,第一个参数是 Promise 对象由 pending 变成 resolved 时调用的回调函数 resolve,第二个参数是 Promise 对象由 pending 变成 rejected 状态的时候调用的回调函数 reject。
promise.then(function(val){
// promise 执行成功的回调函数的内容
},function(err){
// promise 执行失败的回调函数的内容
})
2.3 一个完整的例子
其中当前目录下有一个文件 1.txt 其中的内容是 “111”
const fs = require('fs')
var promise = new Promise(function (resolve, reject) {
console.log("我被立即执行了")
fs.readFile('./1.txt', 'utf-8', (err, data)=>{
if(err)
// 异步操作失败:文件读写失败
reject(err)
else
// 异步操作成功:文件读写成功
resolve(data)
})
})
promise.then(function(val){
console.log(val)
},function(err){
console.log(err)
})
在上述代码中,创建 promise 实例对象后,就会输出“我被立即执行了”。
在 promise 对象中文件的读取是异步操作,把它封装在 promise 对象当中,then 方法指定的成功或者失败时会执行的回调函数。
当readFile方法在执行的时候,由于是异步操作,结果不会立即返回,所以容器的状态是pending。当文件读取完成后,promise容器的状态由 pending 变为 resolved 或者 rejected,此时才会执行相应的回调函数。
这里文件读取成功的结果会输出 “111”(即执行 resolve 函数),若文件 1.txt 不存在则会报错,“Error: ENOENT: no such file or directory“(即执行 reject 函数)。
3. Promise 的 then() 方法与链式调用
Promise 实例具有then
方法,也就是说,then
方法是定义在原型对象Promise.prototype
上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。
可以在 then 方法中传入的回调函数返回一个新的 Promise 实例,这样可以采用链式写法,在 then 方法后可以再调用另外一个 then 方法,同时采用这样写法的第二个 Promise 实例会等待第一个 Promise 实例执行完对象内的异步操作执行回调函数结束后才执行,这样就可以保证两个或者多个异步操作可以按序执行。
const fs = require('fs')
function readFile(file){
return new Promise(function (resolve, reject) {
fs.readFile(file, 'utf-8', (err, data)=>{
if(err)
reject(err)
else
resolve(data)
})
})
}
readFile("1.txt").then(data => {
console.log(data);
return readFile("2.txt")
}).then(data=>{
console.log(data)
})
// 111
// 222
4. Promise 的 catch() 方法
Promise 对象的 then 方法中可以不指定异步操作发生错误的 rejectd 状态下的回调函数,可以在一系列 Promise 对象链式调用的最后使用 catch 方法来捕捉 Promise 对象发生的错误。
readFile("1.txt").then(data => {
console.log(data)
return readFile("2.txt")
}).then(data=>{
console.log(data)
return readFile("3.txt")
}).then(data=>{
console.log(data)
}).catch(err=>{
console.log(err)
})
// catch 方法可以捕捉链式写法中所有 promise 对象发生的错误
// 一旦在链中有 promise 对象发生错误,该链条后的所有 then 方法不再执行,转向执行 catch 方法中的回调函数。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
5. Promise.resolve() 和 Promise.reject() 方法
使用Promise原型对象中的 Promise.resolve() 方法可以把现有对象转为新的 Promise 对象,状态为resolve。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.resolve() 方法的参数分为以下四种情况。
5.1 参数是一个Promise实例
如果参数是一个 Promise 实例,那么 Promise.resolve 将不做任何修改,原封不动地返回这个实例。
5.2 参数是一个带有then方法的对象
例如下面的thenObj:
var thenObj = {
then: function(resolve, reject){
resolve(1);
}
}
此时Promise.resolve() 方法会将这个对象转为 Promise 对象,然后立即执行 thenObj 对象的 then 方法。
5.3 参数不是具有then方法的对象或根本不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,那么Promise.resolve方法返回一个新的Promise对象,状态为Resolved。
var p = Promise.resolve('Hello World');
p.then(function (s){
console.log(s)
});
// 输出 Hello World
5.4 不带任何参数
Promise.resolve 方法允许在调用时不带有参数,而直接返回一个 Resolved 状态的 Promise 对象。
var p = Promise.resolve()
p.then(function(){
// 要执行的代码
});
5.5 Promise.reject()
Promise.reject() 方法也会立即返回一个新的 Promise 实例,状态为 rejected。
其余用法与Promise.resolve的四个用法一致。
6. Promise.all() 和 Promise.race()
Promise.all
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
// p1、p1、p3均为 promise 对象
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用上面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。(Promise.all
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
Promise.race
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态(race竞赛),p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
Promise.race
方法的参数与Promise.all
方法一样,如果不是 Promise 实例,就会先调用上面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。
二、Promise对象的实现
大概理解了Promise 对象的使用方式,可以自己实现一个 Promise 来深入了解 Promise 对象的原理。
下面是我认为写的很好且容易读懂的一段代码,转载自https://blog.youkuaiyun.com/yangbo1993/article/details/79034868
另外一篇宝藏文章:https://www.jianshu.com/p/43de678e918a
/**
* 实现一个Promise
*/
function Promise(task) {
//接收一个处理函数
let that = this;//缓存this
//promise有三种状态 默认为pending
that.status = 'pending';
that.onFulfilledFns = [];//所有成功的回调
that.onRejectedFns = [];//所有失败的回调
that.value = undefined;
function resolve(value) {
//成功函数
if(that.status == 'pending'){
that.status = 'fulfilled';
that.value = value;
//执行所有成功的回调
that.onFulfilledFns.forEach(item=>item(value));
}
};
function reject(reason) {
//失败函数
if(that.status == 'pending'){
that.status = 'rejected';
that.value = reason;
//执行所有失败的回到
that.onRejectedFns.forEach(item=>item(reason));
}
};
//立即执行传入的处理函数
try{
task(resolve,reject);
}catch (err){
reject(err)
}
};
function resolvePromise(promise2,x,resolve,reject) {
let then;
if(promise2 === x){
return reject(new Error('循环引用'));
}
if(x instanceof Promise){
//判断x的prototype所指向的对象是否存在Promise的原型链上
if(x.status= 'pending'){
x.then(function (y) {
//递归 调用
resolvePromise(promise2,y,resolve,reject);
},reject)
}else if(x.status == 'fulfilled'){
resolve(x.value);
}else if(x.status == 'rejected'){
reject(x.value);
}
}else if(x != null && typeof x == 'object' || typeof x == 'function'){
try{
then = x.then;
if(typeof then == 'function'){
then.call(x,function (y) {
resolvePromise(promise2,y,resolve,reject);
},function (y) {
reject(y)
});
}
}catch (e){
reject(e);
}
}else{
resolve(x);
}
}
//then方法
Promise.prototype.then = function (onFulfilled, onRejected) {
//假如没有传入异步处理程序则直接返回结果
onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function (value) {
return value;
};
onRejected = typeof onRejected == 'function'?onRejected:function (reason) {
return reason;
};
var promise2;//用来实现链式调用
let that = this;
if(that.status == 'fulfilled'){
promise2 = new Promise(function (resolve,reject) {
let x = onFulfilled(that.value);
resolvePromise(promise2,x,resolve,reject);
});
}else if(that.status == 'rejected'){
promise2 = new Promise(function (resolve,reject) {
let x = onRejected(that.value);
reject(x);
});
}else if(that.status == 'pending'){
promise2 = new Promise(function (resolve,reject) {
that.onFulfilledFns.push(function(){
let x = onFulfilled(that.value);
resolve(x);
});
that.onRejectedFns.push(function () {
let x = onRejected(that.value);
reject(x);
});
});
}else{
promise2 = new Promise(function (resolve,reject) {
reject('Promise内部状态错误');
});
}
return promise2;
};
Promise.resolve = function (val) {
return new Promise(function (resolve,reject) {
resolve(val);
});
};
Promise.reject = function (val) {
return new Promise(function (resolve,reject) {
reject(val);
});
};
Promise.all = function (arrs) {
//all方法接收一个promise数组,数组中所有异步操作结束后返回一个新的promise
if(typeof arrs == 'object' && arrs.length > 0){
return new Promise(function (resolve,reject) {
let result = [];//新的promise返回结果
let indexNum = 0;//当前完成几个
let resolved = function (index) {
return function (data) {
result[index] = data;
indexNum++;
if(indexNum == arrs.length){
resolve(result);
}
}
};
for(let i=0;i<arrs.length;i++){
arrs[i].then(resolved(i),function (reason) {
reject(reason);
});
};
});
}else{
return new Promise(function (resolve,reject) {
reject(new Error('all方法传入参数错误'));
});
}
};
Promise.race = function (arrs) {
if(typeof arrs == 'object' && arrs.length > 0){
return new Promise(function (resolve,reject) {
for(let i=0;i<arrs.length;i++){
arrs[i].then(function (data) {
resolve(data);
},function (err) {
reject(err);
});
};
});
}else{
return new Promise(function (resolve,reject) {
reject(new Error('race方法传入参数错误'));
})
};
};
module.exports = Promise;