这里我们先啰嗦一下Promise的概念:
什么是promise?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
那如何实现一个符合规范的Promise呢?
参考promiseA+规范总结: 我们知道promise中共有三种状态 pending 过渡态 fulfilled 完成态 rejected 失败态
promise状态改变只有两种可能
过渡态=>成功态
过渡态 => 失败态
过程不可逆 无法相互转化
这里来借用一张图片更容易理解
let promise = new Promise((resolve, reject) => {
//这里放入我们要执行的函数,可能是同步,也可能是异步, 这里我们就来写一个异步的执行
setTimeout(() => {
resolve('hello');
})
})
promise.then(data => {
console.log(data);
}, err => {console.log(err)})
复制代码
上面代码表示我们new一个promise实例,并异步执行 这里通过调用then方法,我们成功得到了结果
观察原生promise用法,我们可以发现,在new Promise时候传入了一个函数,这个函数在规范中的叫法是exector 执行器 看到这里,我们先有一个大概思路,构建一个自己的Promise构造函数
// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
}
复制代码
好的,第一步完成, 重点来了,这个Promise内部到底干了什么呢 可以看到,原生的exector中传入了两个参数,第一个参数执行会让promise状态变为resolve, 也就是成功, 第二个执行会让函数变为reject状态,也就是失败
并且这连个形参执行之后都可以传入参数,我们继续完善代码 我们将这两个形参的函数封装在构造函数内部
// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
// 这里我们将value 成功时候的值 reason失败时候的值放入属性中
let self = this;
this.value = undefined;
this.reason = undefined;
// 成功执行
function resolve(value) {
self.value = value;
}
// 失败执行
function reject(reason) {
self.reason = reason;
}
exector(resolve, reject);
}
复制代码
这里问题来了,我们知道,promise的执行过程是不可逆的,resolve和rejeact之间也不能相互转化, 这里,我们就需要加入一个状态,判断当前是否在pending过程,另外我们的执行器可能直接报错,这里我们也需要处理一下.
// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
// 这里我们将value 成功时候的值 reason失败时候的值放入属性中
let self = this;
// 这里我们加入一个状态标识
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
// 成功执行
function resolve(value) {
// 判断是否处于pending状态
if (self.status === 'pending') {
self.value = value;
// 这里我们执行之后需要更改状态
self.status = 'resolved';
}
}
// 失败执行
function reject(reason) {
// 判断是否处于pending状态
if (self.status === 'pending') {
self.reason = reason;
// 这里我们执行之后需要更改状态
self.status = 'rejected';
}
}
// 这里对异常进行处理
try {
exector(resolve, reject);
} catch(e) {
reject(e)
}
}
复制代码
这里先留个小坑,一会我们回头来补上
好了,Promise基本就是这样,是不是很简单,这里我们先实现一个简易版,后面的功能会逐步添加进去,不要心急,继续往后看
new Promise之后我们怎么去改变promise对象的状态呢, 通过前面原生的用法我们了解到,需要使用then方法, then方法分别指定了resolved状态和rejeacted状态的回调函数 那怎么知道使用哪个回调函数呢,我们刚不是在构造函数内部定义了status么,这里就用上啦,上代码
// 我们将then方法添加到构造函数的原型上 参数分别为成功和失败的回调
Promise.prototype.then = function(onFulfilled, onRejected) {
// 获取下this
let self = this;
if (this.status === 'resolved') {
onFulfilled(self.value);
}
if (this.status === 'rejected') {
onRejected(self.reason);
}
}
复制代码
ok,我们现在可以自己运行试试
let promise = new Promise((resolve, reject) => {
resolve("haha");
})
promise.then(data => {
console.log(data); //输出 haha
}, err=> {
console.log(err);
})
// 多次调用
promise.then(data => {
console.log(data); //输出 haha
}, err=> {
console.log(err);
})
复制代码
上面可以注意到, new Promise中的改变状态操作我们使用的是同步,那如果是异步呢,我们平时遇到的基本都是异步操作,该如何解决?
这里我们需要在构造函数中存放两个数组,分别保存成功回调和失败的回调 因为可以then多次,所以需要将这些函数放在数组中 代码如下:
// 这里我们创建了一个构造函数 参数就是执行器
function Promise(exector) {
// 这里我们将value 成功时候的值 reason失败时候的值放入属性中
let self = this;
// 这里我们加入一个状态标识
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
// 存储then中成功的回调函数
this.onResolvedCallbacks = [];
// 存储then中失败的回调函数
this.onRejectedCallbacks = [];
// 成功执行
function resolve(value) {
// 判断是否处于pending状态
if (self.status === 'pending') {
self.value = value;
// 这里我们执行之后需要更改状态
self.status = 'resolved';
// 成功之后遍历then中成功的所有回调函数
self.onResolvedCallbacks.forEach(fn => fn());
}
}
// 失败执行
function reject(reason) {
// 判断是否处于pending状态
if (self.status === 'pending') {
self.reason = reason;
// 这里我们执行之后需要更改状态
self.status = 'rejected';
// 成功之后遍历then中失败的所有回调函数
self.onRejectedCallbacks.forEach(fn => fn());
}
}
// 这里对异常进行处理
try {
exector(resolve, reject);
} catch(e) {
reject(e)
}
}
// then 改造
Promise.prototype.then = function(onFulfilled, onRejected) {
// 获取下this
let self = this;
if (this.status === 'resolved') {
onFulfulled(self.value);
}
if (this.status === 'rejected') {
onRejected(self.reason);
}
// 如果异步执行则位pending状态
if(this.status === 'pending') {
// 保存回调函数
this.onResolvedCallbacks.push(() => {
onFulfilled(self.value);
})
this.onRejectedCallbacks.push(() => {
onRejected(self.reason)
});
}
}
// 这里我们可以再次实验
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if(Math.random() > 0.5) {
resolve('成功');
} else {
reject('失败');
}
})
})
promise.then((data) => {
console.log('success' + data);
}, (err) => {
console.log('err' + err);
})
复制代码
这里要开始重点了,千万不要错过,通过以上代码,我们实现了一个简易版的promise,说简易版是因为我们的then方法只能调用一次,并没有实现原生promise中的链式调用。
那链式调用是如何实现的呢?
这里我们需要回顾下promiseA+规范,通过查看规范和阮一峰的es6讲解可以了解到
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
这里我们看一段原生promise代码
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
复制代码
上面的代码使用then方法,依次指定了两个回调函数。 第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
另外通过原生的promise我们还可以发现,上一次的成功或者失败在返回值是一个普通类型数据的时候,都走向了下一次then的成功回调,我们可以继续改造then方法
Promise.prototype.then = function(onFulfilled, onRejected) {
// 获取下this
let self = this;
// 因为then方法返回的是一个promise,这里我们新建一个promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
//获取回调的返回值
try {
// 当执行成功回调的时候 可能会出现异常,那就用这个异常作为promise2的错误的结果
let x = onFulfilled(self.value);
//执行完当前成功回调后返回结果可能是promise
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
}
if (this.status === 'rejected') {
//获取回调的返回值
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
}
// 如果异步执行则位pending状态
if(this.status === 'pending') {
// 保存回调函数
this.onResolvedCallbacks.push(() => {
//获取回调的返回值
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
})
this.onRejectedCallbacks.push(() => {
//获取回调的返回值
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
});
}
})
return promise2;
}
复制代码
这里我们看下新的then函数有什么变化,我们一步一步分析,首先,新建了一个promise并返回,这里是根据原生promise文档得知: then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
这里理解之后我们继续看,内部我们又获取了本次then方法成功或者失败回调之后的返回值,赋值给变量x,这里就会出现几种情况,变量x可能为普通值,也可能为一个promise
我们定义了一个resolvePromise函数,将then返回的promise, 本次成功或者失败的返回值,已经then返回promise的两个参数传输这个函数中,进行一些判断,具体实现如下:
function resolvePromise(promise2,x,resolve,reject){
// promise2和函数执行后返回的结果是同一个对象
if(promise2 === x){
return reject(new TypeError('Chaining cycle'));
}
// x可能是一个promise 或者是一个普通值
if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
try{
let then = x.then;
// 取对象上的属性 怎么能报异常呢?(这个promise不一定是自己写的 可能是别人写的 有的人会乱写)
// x可能还是一个promise 那么就让这个promise执行即可
// {then:{}}
// 这里的逻辑不单单是自己的 还有别人的 别人的promise 可能既会调用成功 也会调用失败
if(typeof then === 'function'){
then.call(x,y=>{ // 返回promise后的成功结果
// 递归直到解析成普通值为止
// 递归 可能成功后的结果是一个promise 那就要循环的去解析
resolvePromise(promise2,y,resolve,reject);
},err=>{ // promise的失败结果
reject(err);
});
}else{
resolve(x);
}
}catch(e){
reject(e);
}
}else{ // 如果x是一个常量
resolve(x);
}
}
复制代码
看的这里是不是有点蒙圈,没关系,我们继续分析这个实现。
首选进入函数内部,我们判断promise2是不是等于x, 这个相当于判断上次then的返回值是不是成功和回调的返回值,这样就是陷入死循环,举个例子:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
})
})
let p2 = p.then(data => {
return p2;
})
复制代码
这种写法就会陷入一个死循环 所以要避免这种情况发生。 好的,继续往下看,刚才说到x可能是一个普通值,也可能是一个promise,所以函数内部就要做一个判断,是否是一个promise, 如果返回的是一个promise,那么需要继续执行这个promise,这里用了递归。 平时使用promise时候我们也会注意到,各种promise库可能会混用,所以内部对这个then的类型进行了判断。
Ok,到这里是不是理解了一些了,我们继续往下看,我们知道同一个Promise内部的状态是无法相互转化的,这里需要在内部做一个判断。
function resolvePromise(promise2,x,resolve,reject){
// promise2和函数执行后返回的结果是同一个对象
if(promise2 === x){
return reject(new TypeError('Chaining cycle'));
}
let called;
// x可能是一个promise 或者是一个普通值
if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
try{
let then = x.then; // 取对象上的属性 怎么能报异常呢?(这个promise不一定是自己写的 可能是别人写的 有的人会乱写)
// x可能还是一个promise 那么就让这个promise执行即可
// {then:{}}
// 这里的逻辑不单单是自己的 还有别人的 别人的promise 可能既会调用成功 也会调用失败
if(typeof then === 'function'){
then.call(x,y=>{ // 返回promise后的成功结果
// 递归直到解析成普通值为止
if(called) return; // 防止多次调用
called = true;
// 递归 可能成功后的结果是一个promise 那就要循环的去解析
resolvePromise(promise2,y,resolve,reject);
},err=>{ // promise的失败结果
if(called) return;
called = true;
reject(err);
});
}else{
resolve(x);
}
}catch(e){
if(called) return;
called = true;
reject(e);
}
}else{ // 如果x是一个常量
resolve(x);
}
}
复制代码
我们加入一个called变量,防止互相转化。 代码写到这里是不是就完了? 当然没有,细心的同学会发现,原生promise还有一个用法
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
})
})
p.then().then(data => {
console.log(data);
throw new Error('e');
}).then().then(null, err => {
console.log(err);
})
复制代码
这种用法会发生值穿透,当上一个then函数没有调用成功和失败回调的时候,值会传递进下一次then调用。
这个怎么实现的呢,其实很简单,我们只需要判断每次then调用的时候是否传入了成功或者失败的回调,没有回调,就继续返回上轮then成功或者失败传入的值。 我们还了解到,then方法的回调都是异步执行的,这里我们简单用定时器模仿下,当然内部实现可不是这么简单。这里仅作为简单实现
代码如下
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
let self = this;
let promise2;
promise2 = new Promise((resolve, reject) => {
if (self.status === 'resolved') {
setTimeout(()=>{
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
if (self.status === 'rejected') {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
self.onRejectedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
}
});
return promise2
}
复制代码
大工告成。
等等,是不是少点什么,你是不是在逗我,少侠莫急。且继续往下看。 我们平时使用当然还有promise的一些其他方法,如catch all race。 而且还能这么写Promise.resove().then()
Promise.reject().then()
复制代码
我们一个一个来实现,就先看上面直接在Promise类上调用成功和失败 我们可以这么写
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
复制代码
catch呢, 相当于直接走入下一次then的失败回调
Promise.prototype.catch = function(onRejected){
// 默认不写成功
return this.then(null,onRejected);
};
复制代码
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 具体用法可以参考es6文档,这就不具体再说用法
// 传入一个promise数组
Promise.all = function(promises){
// 返回执行后的结果
return new Promise((resolve,reject)=>{
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
// 判断是否全部成功
if(++i == promises.length){
resolve(arr);
}
}
for(let i = 0;i<promises.length;i++){
promises[i].then(data=>{ // data是成功的结果
//将每次执行成功后的结果传入函数
processData(i,data);
},reject);
}
})
}
复制代码
race就更简单了。
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(resolve,reject);
}
})
}
复制代码
这里我们就已经实现了promise常见的一些功能,这里需要多看几遍加深记忆。