这里咱们先啰嗦一下Promise的概念:es6
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最先提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。编程
那如何实现一个符合规范的Promise呢?json
咱们知道promise中共有三种状态 pending 过渡态 fulfilled 完成态 rejected 失败态数组
promise状态改变只有两种可能promise
过渡态=>成功态bash
过渡态 => 失败态dom
过程不可逆 没法相互转化异步
这里来借用一张图片更容易理解异步编程
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.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常见的一些功能,这里须要多看几遍加深记忆。