网站建设与网站设计,成都住建局官网住建智慧建管,湛江公司做网站,wordpress页面属性一#xff0c;背景
1.1#xff0c;js的单线程
这一切#xff0c;要从js诞生之初说起#xff0c;因为js是单线程的语言。
js单线程原因#xff1a;作为浏览器脚本语言#xff0c;JavaScript的主要用途是与用户互动#xff0c;以及操作DOM。这决定了它只能是单线程背景
1.1js的单线程
这一切要从js诞生之初说起因为js是单线程的语言。
js单线程原因作为浏览器脚本语言JavaScript的主要用途是与用户互动以及操作DOM。这决定了它只能是单线程否则会带来很复杂的同步问题。比如假定JavaScript同时有两个线程一个线程在某个DOM节点上添加内容另一个线程删除了这个节点这时浏览器应该以哪个线程为准当然这可以加锁来解决但是加锁会变得更复杂得不偿失。1.2同步异步的讨论
先简单理解下同步和异步
同步操作可以理解为不花费时间的操作而异步操作是需要一定时间的。单线程意味着所有的任务都只能乖乖排队执行就和我们排队过地铁闸机一样。但是有的人刷卡有的人刷码有的人刷脸所花费的时间不一样假如一个人手机没网了就会卡住队伍造成堵塞。这就是单线程带来的同步阻塞问题。
为了解决这个问题js引入了回调函数机制对于一个IO操作比如一个ajax当发出一个异步请求后程序不会阻塞在那里等待结果的返回而是继续执行下面的代码。当请求成功获取到结果后就会调用回调函数来处理后面的事情这个就是异步非阻塞。在js中异步操作的处理都是异步非阻塞。
所以可以说js是单线程异步编程语言。
如下代码
console.log(1)
setTimeout((){console.log(2)
},100)
console.log(3)
//执行结果132setTimeout里面执行的就是异步操作的回调函数等待完成后执行其中的代码。类似于地铁过闸机没网的那个兄弟先站到旁边处理下网络问题解决了再排队进闸机这样就不会堵着后续的人。
1.3回调函数
那如果异步操作完成后我们想要用这个结果做一些事情呢有人说可以把代码写在异步操作的结果处理里面但是这样代码一层包一层的很难复用和维护。
为了解决这个问题有人提出了回调函数的方案。
其实上文中的setTimeout里面的箭头函数就是一个回调函数但是不够直观这里再举个例子。
异步耗时的工作常见的有以下这些
定时器
建立网络连接
向文件读写数据可以拿向文件读写数据来举例先看如下代码
const fs require(fs)
function printA(){console.log(a)
}
function readFile(){fs.readFile(test.txt, (err, data) {if (err) {console.error(err)return}// data 是二进制类型需要转换成字符串console.log(读取到的文本内容:,data.toString())a1})
}
let a0
readFile()
printA()从上文可以知道a虽然被修改了但它是在读文件操作异步操作中被修改的因为js异步非阻塞的特性在执行printA中的打印a时它并没有执行所以打印出来的a将是0。
那如何把a1打印出来呢
我们可以把一个函数作为参数传给readFile在readFile函数中执行打印操作。
const fs require(fs)
function printA(){console.log(a)
}
function readFile(callback){fs.readFile(test.txt, (err, data) {if (err) {console.error(err)return}// data 是二进制类型需要转换成字符串console.log(读取到的文本内容:,data.toString())a1callback()//这就是传进来的回调函数})
}
let a0
readFile(printA)这就是回调函数函数体在完成某种操作后由内向外调用某个外部函数因为是在函数体内部调用所以能由内向外获取到相关变量作用域链。
简单点说就是把函数当作参数传递给异步操作完成之后再执行。
1.4js的事件循环机制
上文说到js的异步非阻塞机制那它是如何实现就一个主线程完美运行所有的同步异步代码呢
这就需要理解下js的事件循环机制也就是人们所说的Event Loop,可以用下图
常见的微任务
Promises.(then catch finally)process.nextTick MutationObserver
DOM渲染前触发这里值得注意的是promise内是同步而.then后才是微任务。
常见的宏任务
整体代码scriptsetTimeoutsetInterval setImmediateI/OUI renderingnew
DOM渲染后触发也就是说遇到异步函操作还需要判断是宏任务还是微任务宏任务的话就把异步操作的结果加入宏任务队列微任务的话就加入到微任务队列。 于是异步到的队列就由原来的一个事件队列变成了宏队列和微队列两个而主线程空了的话会先去微队列中查找若在这个过程中微队列的事件又产生的新的微任务加入队尾也会在本次循环中进行处理简而言是就是把每轮循环把微队列搞空然后再去宏队列中查找而每次宏任务只执行一个就又轮到微任务队列。
第一步 主线程执行同步任务的同时把一些异步任务放入‘任务队列’task queue中等待主线程的调用栈为空时再依次从队列出去任务去执行
第二步检测任务队列中的微队列是否为空若不为空则取出一个微任务入栈执行然后继续执行第2步如果微队列为空则开始取出宏队列中的一个宏任务执行
第三步执行完宏队列中的一个宏任务后会继续检测微队列是否为空如果有新插入的任务这继续执行第二步如果微队列为空则继续执行宏队列中的下一个任务然后再继续循环执行第三步示例
const fs require(fs)
setTimeout(function callback(){console.log(2)//宏任务Promise.resolve().then((){console.log(6)//宏任务中微任务})
}, 0)
setTimeout(function callback(){console.log(又一个宏任务)//宏任务
}, 0)
new Promise((resolve, reject) {console.log(3)//同步resolve()
})
.then(res {console.log(4);//微任务new Promise((resolve, reject) {console.log(8)//同步fs.readF ile(test.txt, (err, data) {if (err) {console.error(err)return}// data 是二进制类型需要转换成字符串console.log(读取到的文本内容:,data.toString())resolve(data.toString())//宏任务})}).then(res {new Promise((resolve, reject) {console.log(测试)//同步resolve()}).then(res {console.log(再测试);//微任务})})
})
console.log(5)//同步打印的值
3
5
4
8
2
6
又一个宏任务
读取到的文本内容: 测试文本
测试
再测试1.5回调函数的问题和promise的诞生
自此js已经能够依托事件循环机制执行所有的任务。但是因为有的任务需要依托异步任务返回的结果所以我们采用了回调函数的解决方案那如果有多重异步操作并且最后的操作依赖于这几次异步操作。那么就容易层层嵌套带来回调地狱的问题。
如下代码我创建了三个txt文件然后需要依次读取文件内容并打印如果采取回调函数的写法将是如下样子
const fs require(fs)
fs.readFile(test.txt, (err,data) {let reader1data.toString()fs.readFile(test1.txt, (err,data) {let reader2data.toString()fs.readFile(test2.txt, (err,data) {let reader3data.toString()setTimeout((){let resultreader1reader1reader3console.log(获得的结果,result)},10)})})
})fs模块的readFile的第二个参数便是一个回调函数为了按照顺序读取文本内容则需要多层嵌套这样只有几次倒还好实际项目中却容易出现多层导致代码出现回调地狱难以阅读维护。
为了解决这个问题promise应运而生。
回调地狱的产生原因还是回调函数对结果的处理和异步操作终究还是在一起并没有把分离。而引入promise的最大作用就是把异步操作的过程和结果做到了分离可以用promise.then()来获取和处理异步操作的结果。使用promise修改上文的代码
const fs require(fs)
function readFile(fileName){return new Promise((resolve,reject){fs.readFile(fileName, (err,data) {resolve(data.toString())})})
}
let res1,res2,res3
readFile(test.txt)
.then((res){res1resreturn readFile(test1.txt)
})
.then((res){res2resreturn readFile(test2.txt)
})
.then((res){res3resconsole.log(结果,res1res2res3)
})这样一来就把异步的操作和结果通过.then进行了分离。避免了回调地狱的产生。
二promise的then方法实现
那promise到底是个啥为何能有如此效果呢
2.1promise的三种状态
pending: 一个promise在resolve或者reject前就处于这个状态。
fulfilled: 一个promise被resolve后就处于fulfilled状态这个状态不能再改变而且必须拥有一个不可变的值(value)。
rejected: 一个promise被reject后就处于rejected状态这个状态也不能再改变而且必须拥有一个不可变的拒绝原因(reason)。2.2promise.then方法
一个promise必须拥有一个then方法来访问他的值或者拒绝原因。then方法有两个参数
promise.then(onFulfilled, onRejected)并且这两个参数有以下特征
1onFulfilled 和 onRejected 都是可选参数。若不存在则忽略。
2onFulfilled在promise结束前不可调用结束后必须被调用其第一个参数为 promise 的终值value且调用次数不超过一次。
3onRejected在被拒绝前不可被调用拒绝执行后其必须被调用其第一个参数为 promise 的原因reason且调用册数不超过一次。而这个then方法还支持以下特征
1then 方法必须返回一个 promise 对象。这样才支持多次then链式调用。
2then 方法可以被同一个 promise 调用多次。当 promise 成功执行时所有 onFulfilled 需按照其注册顺序依次回调当 promise 被拒绝执行时所有的 onRejected 需按照其注册顺序依次回调。2.3先写出基本的promise实现异步操作与结果的分离
我们平时使用promise是这样的
const p1 new Promise((resolve, reject) {console.log(create a promise);setTimeout((){console.log(异步)resolve(成功了);},10)})
const p2 p1.then((res){console.log(res)
})所以promise接收一个函数该函数有两个参数resolve和reject并且同步执行这个函数。
// 先定义三个常量表示状态
var PENDING pending;//等待中
var FULFILLED fulfilled;//执行完成
var REJECTED rejected;//拒绝执行function MyPromise(fn) {this.status PENDING; // 初始状态为pendingthis.value null; // 初始化valuethis.reason null; // 初始化reason//同步执行这个函数try {fn(resolve, reject);} catch (error) {reject(error);}
}
exports.MyPromiseMyPromise可以看到fn(resolve, reject)异步操作执行完毕后会调用resolve和reject于是promise里面就需要创建resolve和reject提供调用。
// 这两个方法直接写在构造函数里面
function MyPromise(fn) {// ...省略前面代码...// 存一下this,以便resolve和reject里面访问var that this;// resolve方法参数是valuefunction resolve(value) {if(that.status PENDING) {that.status FULFILLED;that.value value;}}// reject方法参数是reasonfunction reject(reason) {if(that.status PENDING) {that.status REJECTED;that.reason reason;}}
}这样一来就能在执行完promise包裹的异步操作后调用resolve或者reject,从而改变promise的状态并且取到结果value或原因reason。
但是就目前的代码而言还需要then方法来获取结果并处理。
根据我们前面的分析then方法可以链式调用所以他是实例方法而且规范中的API是promise.then(onFulfilled, onRejected)我们先把架子搭出来
MyPromise.prototype.then function(onFulfilled, onRejected) {}如此我们在promise中会执行异步操作异步操作完成后会调用resolve方法修改promise的状态并且把结果存在promise的value上。那现在如何在then方法中取得这个结果并执行呢
首先需要判断该异步操作已经执行完毕通过promise的状态变成FULFILLED。异步操作的结果这时候已经存储到promise的value上了于是只需要then的参数是函数把这个value传进去即可。
MyPromise.prototype.then function(onFulfilled, onRejected) {if(this.status FULFILLED) {onFulfilled(this.value)//将结果传入回调函数}if(this.status REJECTED) {onRejected(this.reason);}
}我们现在来试试现在写的promise,写出如下代码
var MyPromise require(./test.js).MyPromise;
const p1 new MyPromise((resolve, reject) {console.log(create a promise);setTimeout((){console.log(异步)resolve(成功了);},10)})
console.log(直接打印,p1)
setTimeout((){console.log(过两s后,p1)const aaap1.then((value){console.log(value)})
},2000)打印的结果 注意到这里我们是使用setTimeout包裹了then函数这意味着现在只是将结果取到了then中进行处理还不能等待异步完成后再执行then中的代码。
到这里就可以大致知道promise到底是个啥?
可以把它当作一个异步操作的处理站点。
初始时状态是pending当执行完毕异步操作用户定义后调用resolve/reject修改状态变成fulfilled/rejected并且把结果存储在value/reason。
然后then方法判断异步操作是否完成完成的话就取value/reason来完成用户接下来的操作用户定义。
其实还是利用的回调函数把promsie当作中转站将异步操作和结果隔离开来。异步操作放在promise的入参函数中结果放value/reasonthen方法需要用到了再去这里取。 2.4then方法的回调函数收集与执行
实际上我们使用promise的时候不会像上文那样用一个setTimeout来包裹更多时候如下使用
new Promise(fn).then(onFulfilled, onRejected);上面代码then是在实例对象一创建好就调用了这时候fn里面的异步操作可能还没结束也就是说他的status还是PENDING这时候肯定不能执行then里面的代码因为结果还没有存储到value上。
那怎么在异步操作完成后再执行then里面的代码呢之前说过resolve和reject方法就是在异步操作完成后调用的所以可以在这里执行then传入的回调函数。
于是修改我们的promise:
// 构造函数
function MyPromise(fn) {// ...省略其他代码...// 构造函数里面添加两个数组存储成功和失败的回调this.onFulfilledCallbacks [];this.onRejectedCallbacks [];function resolve(value) {if(that.status PENDING) {// ...省略其他代码...// resolve里面将所有成功的回调拿出来执行that.onFulfilledCallbacks.forEach(callback {callback(that.value);});}}function reject(reason) {if(that.status PENDING) {// ...省略其他代码...// resolve里面将所有失败的回调拿出来执行that.onRejectedCallbacks.forEach(callback {callback(that.reason);});}}
}// then方法
MyPromise.prototype.then function(onFulfilled, onRejected) {// ...省略其他代码...// 如果还是PENDING状态将回调保存下来if(this.status PENDING) {this.onFulfilledCallbacks.push(onFulfilled);this.onRejectedCallbacks.push(onRejected);}
}也就是说promise.then做的事情就是把传入的回调函数收集起来然后在resolve或reject中取出收集好的回调函数来执行。
这样一来。就能保证then中的回调函数在异步操作结束后再执行。到这里我们已经初步实现了promise最核心的异步操作过程与结果的分离。 2.5then的入参和返回
2.5.1then的入参
之前说过then方法的入参不是函数则忽略其实所谓“忽略”并不是什么都不干对于onFulfilled来说“忽略”就是将value原封不动的返回对于onRejected来说就是返回reasononRejected因为是错误分支我们返回reason应该throw一个Error所以这里需要重写这两个入参
MyPromise.prototype.then function(onFulfilled, onRejected) {// 如果onFulfilled不是函数给一个默认函数返回valuevar realOnFulfilledtypeof onFulfilled function ? onFulfilled : valuevalue// 如果onRejected不是函数给一个默认函数返回reason的Errorvar realOnRejected typeof onRejected function ? onRejected : reason{throw reason}//...省略其他代码
}2.5.2then的返回得是promise
then的返回值必须是一个promise(这里使用promise2)。且如果onFulfilled或者onRejected抛出一个异常e则这个新的promise2必须拒绝执行并且返回原因e。也就是把onFulfilled和onRejected用try…catch包裹一层。
MyPromise.prototype.then function(onFulfilled, onRejected) {// 如果onFulfilled不是函数给一个默认函数返回valuevar realOnFulfilledtypeof onFulfilled function ? onFulfilled : valuevalue// // 如果onRejected不是函数给一个默认函数返回reason的Errorvar realOnRejected typeof onRejected function ? onRejected : reason{throw reason}var thatthisvar promise2 new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错需要rejectif(that.status FULFILLED) {try {realOnFulfilled(that.value);} catch (error) {reject(error);} }//如果异步已经失败则直接执行回调函数,期间有报错需要rejectif(that.status REJECTED) { try {realOnRejected(that.reason);} catch (error) {reject(error);}}// 如果还是PENDING状态将回调保存下来,为了捕获回调中的错误需要try...catch包裹一层if(that.status PENDING) {that.onFulfilledCallbacks.push(function() {try {realOnFulfilled(that.value);} catch (error) {reject(error);}});that.onRejectedCallbacks.push(function() {try {realOnRejected(that.reason);} catch (error) {reject(error);}});}})return promise2
}2.5.3then的入参默认是函数
而如果onFulfilled不是函数且promise1成功执行那么promise2必须成功执行并且返回相同的值。这时候就需要直接透传resolvethis.value给promise2了。
如果 onRejected 不是函数且 promise1 拒绝执行 promise2 必须拒绝执行并返回相同的原因。
MyPromise.prototype.then function(onFulfilled, onRejected) {// 如果onFulfilled不是函数给一个默认函数返回valuevar realOnFulfilledtypeof onFulfilled function ? onFulfilled : valuevalue// // 如果onRejected不是函数给一个默认函数返回reason的Errorvar realOnRejected typeof onRejected function ? onRejected : reason{throw reason}var thatthis//如果异步已经成功则直接执行回调函数,期间有报错需要rejectif(this.status FULFILLED) {var promise2 new MyPromise(function(resolve, reject) {try {//then方法传入非函数且成功执行则把结果传给promise2if (typeof onFulfilled ! function) {resolve(that.value);} else {realOnFulfilled(that.value);resolve(that.value);}} catch (error) {reject(error);} });return promise2;}//如果异步已经失败则直接执行回调函数,期间有报错需要rejectif(this.status REJECTED) { var promise2 new MyPromise(function(resolve, reject) {try {//then方法传入非函数且成功执行则把结果传给promise2if (typeof onRejected ! function) {reject(that.reason);} else {realOnRejected(that.reason);//promise1的onRejected执行成功后promise2应该被resolve,这样promise2的状态才会是fulfilledresolve();}} catch (error) {reject(error);}});return promise2;}// 如果还是PENDING状态将回调保存下来,为了捕获回调中的错误需要try...catch包裹一层if(this.status PENDING) {var promise2 new MyPromise(function(resolve, reject) {that.onFulfilledCallbacks.push(function() {try {//如果传入的不是函数则透传结果给promise2if (typeof onFulfilled ! function) {resolve(that.value);} else {realOnFulfilled(that.value);resolve(that.value);}} catch (error) {reject(error);}});that.onRejectedCallbacks.push(function() {try {//如果传入的不是函数则透传结果给promise2if (typeof onRejected ! function) {reject(that.reason);} else {realOnRejected(that.reason);resolve()}} catch (error) {reject(error);}});});return promise2;}}2.5.4如果传入then的回调函数有返回值
如果 onFulfilled 或者 onRejected 返回一个值 x 需要区分x的类别运行下面的 Promise 解决过程
MyPromise.prototype.then function(onFulfilled, onRejected) {// 如果onFulfilled不是函数给一个默认函数返回valuevar realOnFulfilledtypeof onFulfilled function ? onFulfilled : valuevalue// // 如果onRejected不是函数给一个默认函数返回reason的Errorvar realOnRejected typeof onRejected function ? onRejected : reason{throw reason}var thatthisvar promise2 new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错需要rejectif(that.status FULFILLED) {try {if (typeof onFulfilled ! function) {resolve(that.value);} else {var x realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);}} catch (error) {reject(error);} }//如果异步已经失败则直接执行回调函数,期间有报错需要rejectif(that.status REJECTED) { try {if (typeof onRejected ! function) {reject(that.reason);} else {var x realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}}// 如果还是PENDING状态将回调保存下来,为了捕获回调中的错误需要try...catch包裹一层if(that.status PENDING) {that.onFulfilledCallbacks.push(function() {try {if (typeof onFulfilled ! function) {resolve(that.value);} else {var x realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}});that.onRejectedCallbacks.push(function() {try {if (typeof onRejected ! function) {reject(that.reason);} else {var x realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}});}})return promise2
}在规范中还有一条onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用。这一条的意思是实践中要确保 onFulfilled 和 onRejected 方法异步执行且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。所以在我们执行onFulfilled 和 onRejected的时候都应该包到setTimeout里面去。
按照我个人的理解这里包裹个setTimeout是为了让resolvePromise(promise2, x, resolve, reject);这里传入的promise2不是undefined。
于是then方法变成
MyPromise.prototype.then function(onFulfilled, onRejected) {// 如果onFulfilled不是函数给一个默认函数返回valuevar realOnFulfilledtypeof onFulfilled function ? onFulfilled : valuevalue// // 如果onRejected不是函数给一个默认函数返回reason的Errorvar realOnRejected typeof onRejected function ? onRejected : reason{throw reason}var thatthisvar promise2 new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错需要rejectif(that.status FULFILLED) {setTimeout(function() {try {if (typeof onFulfilled ! function) {resolve(that.value);} else {var x realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);}} catch (error) {reject(error);} },0)}//如果异步已经失败则直接执行回调函数,期间有报错需要rejectif(that.status REJECTED) { setTimeout(function() {try {if (typeof onRejected ! function) {reject(that.reason);} else {var x realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}},0)}// 如果还是PENDING状态将回调保存下来,为了捕获回调中的错误需要try...catch包裹一层if(that.status PENDING) {that.onFulfilledCallbacks.push(function() {setTimeout(function () {try {if (typeof onFulfilled ! function) {resolve(that.value);} else {var x realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});that.onRejectedCallbacks.push(function() {setTimeout(function () {try {if (typeof onRejected ! function) {reject(that.reason);} else {var x realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});}})return promise2
}2.6Promise 解决过程
从上文看Promise 解决过程是为了处理then传入的函数带返回值的情况。于是针对不同的返回值需要有不同的处理方案。实现的原则就是一点让then方法返回的promise2的值接收then入参函数的返回值。
所以resolvePromise这样写
resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程把promise2传入把then入参函数的返回x可能没有传入然后传入promise2的resolve和reject利用它来修改promise2的状态和值。
2.6.1如果promise和x引用同一个对象则用TypeError作为原因拒绝rejectpromise。
function resolvePromise(promise, x, resolve, reject) {// 如果 promise 和 x 指向同一对象以 TypeError 为据因拒绝执行 promise// 这是为了防止死循环if (promise x) {return reject(new TypeError(The promise and the return value are the same));}
}2.6.2如果then入参函数返回的是promise
如果x是一个promise,则需要递归逐层展开返回的promise取得结果y并且把promise2和它的resolve和reject传入只有这样promise2才能得到最后的结果
function resolvePromise(promise, x, resolve, reject) {// 如果 promise 和 x 指向同一对象以 TypeError 为据因拒绝执行 promise// 这是为了防止死循环if (promise x) {return reject(new TypeError(The promise and the return value are the same));}if (x instanceof MyPromise) {// 如果 x 为 Promise 则让then返回的 promise 接受 x 的状态和结果// 也就是继续执行x如果执行的时候拿到一个y还要继续解析yx.then(function (y) {//注意这里传入的就是入参promise,也就是修改这个promise的状态和结果,简单写就是 resolve(y)resolvePromise(promise, y, resolve, reject);}, reject);}
}2.6.3如果x是个对象或者方法这里处理的是类promise对象即具备then方法的对象。
2.3.3另外如果x是个对象或者方法这里处理的是类promise对象即具备then方法的对象。2.3.3.1 让x作为x.then2.3.3.2 如果取回的x.then属性的结果为一个异常e,用e作为原因reject promise2.3.3.3 如果then是一个方法把x当作this来调用它 第一个参数为 resolvePromise第二个参数为rejectPromise,其中:2.3.3.3.1 如果/当 resolvePromise被一个值y调用运行 [[Resolve]](promise, y)2.3.3.3.2 如果/当 rejectPromise被一个原因r调用用r拒绝rejectpromise2.3.3.3.3 如果resolvePromise和 rejectPromise都被调用或者对同一个参数进行多次调用第一次调用执行任何进一步的调用都被忽略2.3.3.3.4 如果调用then抛出一个异常e,2.3.3.3.4.1 如果resolvePromise或 rejectPromise已被调用忽略。2.3.3.3.4.2 或者 用e作为reason拒绝rejectpromise2.3.3.4 如果then不是一个函数用x完成(fulfill)promise这里规范中讲得很复杂其实就是为了处理返回的是类promsie(thenable)对象。
如下代码
var thenableA {name: thenableA,then: function (resolve, reject) {console.log(Iam ${this.name});resolve(this.name)}
}
var p1new MyPromise((resolve, reject) {console.log(create promise1);resolve(1)
})
p1.then((res) {return thenableA
})打印出来就是
create promise1
Iam thenableA具体的then方法实现的内容就是把thenable对象像promise一样展开执行其中的then方法同样是让then方法返回的promise2的值接收最终的值。
function resolvePromise(promise, x, resolve, reject) {//...其他代码// 如果 x 为对象或者函数即可能是thenable的对象/函数else if (x (typeof x object || typeof x function)) {var called false;try {// 把 x.then 赋值给 then var then x.then;// 如果 then 是函数则返回的其实是类promise(thenable)对象需要展开执行其中的then方法if (typeof then function) {then.call(x,function (y) {if (called) return;called true;resolvePromise(promise, y, resolve, reject);//把它的then方法执行掉并且修改promise2的值和状态},function (r) {if (called) return;called true;reject(r);});} else {// 如果 then 不是函数then返回的不是thenable对象以 x 为参数执行 promiseif (called) return;called true;resolve(x);}} catch (error) {// 如果取 x.then 的值时抛出错误 e 则以 e 为据因拒绝 promise,return是为了不执行后续的代码if (called) return;called true;return reject(error);}
}例如上文代码
var thenableA {name: thenableA,then: function (resolve, reject) {console.log(Iam ${this.name});resolve(this.name)}
}
var p1new MyPromise((resolve, reject) {console.log(create promise1);resolve(1)
})
var p2p1.then((res) {return thenableA
})setTimeout((){console.log(,p2)
},0)执行之后打印的是
create promise1
Iam thenableAMyPromise {status: fulfilled,value: thenableA,reason: null,onFulfilledCallbacks: [],onRejectedCallbacks: []
}可以看到thenableA的then方法被执行并且改变了promise2的值于是p2的value变成了’thenableA’。
2.6.4返回的x不是函数也不是对象甚至没有返回x为undefined
如果 x既不是对象也不是函数用x完成(fulfill)promise也就是直接resolve(x)即可。
三验证promiseA规范
在我们写好的MyPromsie中添加如下代码
MyPromise.deferred function() {var result {};result.promise new MyPromise(function(resolve, reject){result.resolve resolve;result.reject reject;});return result;
}
module.exports MyPromise//记得这一步必须这样导出不然会报错。然后项目目录安装测试库
npm i promises-aplus-tests -g于是我们的手手写promise完整内容如下 // 先定义三个常量表示状态
var PENDING pending;
var FULFILLED fulfilled;
var REJECTED rejected;function MyPromise(fn) {this.status PENDING; // 初始状态为pendingthis.value null; // 初始化valuethis.reason null; // 初始化reason// 构造函数里面添加两个数组存储成功和失败的回调this.onFulfilledCallbacks [];this.onRejectedCallbacks [];// 存一下this,以便resolve和reject里面访问var that this;// resolve方法参数是valuefunction resolve(value) {if(that.status PENDING) {that.status FULFILLED;that.value value;// resolve里面将所有成功的回调拿出来执行that.onFulfilledCallbacks.forEach(callback {callback(that.value);});}}// reject方法参数是reasonfunction reject(reason) {if(that.status PENDING) {that.status REJECTED;that.reason reason;// resolve里面将所有失败的回调拿出来执行that.onRejectedCallbacks.forEach(callback {callback(that.reason);});}}try {fn(resolve, reject);} catch (error) {reject(error);}
}
MyPromise.prototype.then function(onFulfilled, onRejected) {// 如果onFulfilled不是函数给一个默认函数返回valuevar realOnFulfilledtypeof onFulfilled function ? onFulfilled : valuevalue// // 如果onRejected不是函数给一个默认函数返回reason的Errorvar realOnRejected typeof onRejected function ? onRejected : reason{throw reason}var thatthisvar promise2 new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错需要rejectif(that.status FULFILLED) {setTimeout(function() {try {if (typeof onFulfilled ! function) {resolve(that.value);} else {var x realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);}} catch (error) {reject(error);} },0)}//如果异步已经失败则直接执行回调函数,期间有报错需要rejectif(that.status REJECTED) { setTimeout(function() {try {if (typeof onRejected ! function) {reject(that.reason);} else {var x realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}},0)}// 如果还是PENDING状态将回调保存下来,为了捕获回调中的错误需要try...catch包裹一层if(that.status PENDING) {that.onFulfilledCallbacks.push(function() {setTimeout(function () {try {if (typeof onFulfilled ! function) {resolve(that.value);} else {var x realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});that.onRejectedCallbacks.push(function() {setTimeout(function () {try {if (typeof onRejected ! function) {reject(that.reason);} else {var x realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});}})return promise2
}function resolvePromise(promise, x, resolve, reject) {// 如果 promise 和 x 指向同一对象以 TypeError 为据因拒绝执行 promise// 这是为了防止死循环if (promise x) {return reject(new TypeError(The promise and the return value are the same));}if (x instanceof MyPromise) {console.log(是MyPromise)// 如果 x 为 Promise 则让then返回的 promise 接受 x 的状态和结果// 也就是继续执行x如果执行的时候拿到一个y还要继续解析yx.then(function (y) {//注意这里传入的就是入参promise,也就是修改这个promise的状态和结果,简单写就是 resolve(y)resolvePromise(promise, y, resolve, reject);}, reject);}// 如果 x 为对象或者函数else if (x (typeof x object || typeof x function)) {var called false;try {// 把 x.then 赋值给 then var then x.then;// 如果 then 是函数则返回的其实是类promise(thenable)对象需要展开执行其中的then方法if (typeof then function) {then.call(x,function (y) {if (called) return;called true;resolvePromise(promise, y, resolve, reject);},function (r) {if (called) return;called true;reject(r);});} else {// 如果 then 不是函数then返回的不是thenable对象以 x 为参数执行 promiseif (called) return;called true;resolve(x);}} catch (error) {// 如果取 x.then 的值时抛出错误 e 则以 e 为据因拒绝 promise,return是为了不执行后续的代码if (called) return;called true;return reject(error);}} else {// 如果 x 不为对象或者函数以 x 为参数执行 promise没有返回时也执行这个resolve(x);}
}MyPromise.deferred function() {var result {};result.promise new MyPromise(function(resolve, reject){result.resolve resolve;result.reject reject;});return result;
}
module.exports MyPromise项目根目录执行测试
promises-aplus-tests test.jstest.js是我的文件名可以看到通过了全量的测试用例。 四promise.resolve的实现
它的作用是将现有对象转为Promise对象。
即如果是promise则直接返回如果不是则返回值为这个的promise。
MyPromise.resolve function(parameter) {if(parameter instanceof MyPromise) {return parameter;}return new MyPromise(function(resolve) {resolve(parameter);});
}即
var promise1 new MyPromise((resolve, reject) {setTimeout((){resolve(2)},10)
})
var p2MyPromise.resolve(promise1)
p2.then((res){console.log(res)//打印2
})五Promise.reject的实现
返回一个新的Promise实例该实例的状态为rejected。Promise.reject方法的参数reason会被传递给实例的回调函数。
MyPromise.reject function(reason) {return new MyPromise(function(resolve, reject) {reject(reason);});
}六Promise.all的实现
该方法用于将多个 Promise 实例包装成一个新的 Promise 实例。它接受一个数组作为参数p1、p2、p3都是 Promise 实例如果不是就会先调用Promise.resolve方法将参数转为 Promise 实例再进一步处理。当p1, p2, p3全部resolve外层的promise才resolve有任何一个reject外层的promise都reject。
const p Promise.all([p1, p2, p3]);MyPromise.all function(promiseList) {var resPromise new MyPromise(function(resolve, reject) {var count 0;var result [];var length promiseList.length;if(length 0) {return resolve(result);}promiseList.forEach(function(promise, index) {MyPromise.resolve(promise).then(function(value){count;result[index] value;if(count length) {//全部执行完毕后才resolve结果数组出去resolve(result);}}, function(reason){reject(reason);});});});return resPromise;
}使用的示例
var p1 new MyPromise((resolve, reject) {setTimeout((){resolve(1)},10)
})
var p2 new MyPromise((resolve, reject) {setTimeout((){resolve(2)},0)
})
var p3 new MyPromise((resolve, reject) {setTimeout((){resolve(3)},20)
})var resultMyPromise.all([p1,p2,p3])
setTimeout((){console.log(result)//打印返回的所有结果
},1000)
result.then((res){console.log(res)
})得到的结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0tyj5fd-1680622612470)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230329225315698.png)]
七Promise.race方法
和promise.all的用法一样传入一个promsie数组不同的是promise.race是一旦有一个promise执行完毕后就返回结果。
MyPromise.race function(promiseList) {var resPromise new MyPromise(function(resolve, reject) {var length promiseList.length;if(length 0) {return resolve();} else {for(var i 0; i length; i) {//哪个先成功就直接resolve结果了因为return了就不执行后续的结果了不用foreach是因为它无法returnMyPromise.resolve(promiseList[i]).then(function(value) {return resolve(value);}, function(reason) {return reject(reason);});}}});return resPromise;
}使用的示例
var p1 new MyPromise((resolve, reject) {setTimeout((){resolve(1)},10)
})
var p2 new MyPromise((resolve, reject) {setTimeout((){resolve(2)},0)
})
var p3 new MyPromise((resolve, reject) {setTimeout((){resolve(3)},20)
})var resultMyPromise.race([p1,p2,p3])
setTimeout((){console.log(result)//打印返回的所有结果
},1000)
result.then((res){console.log(res)
})八Promise.prototype.catch
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名用于指定发生错误时的回调函数。
MyPromise.prototype.catch function(onRejected) {this.then(null, onRejected);
}九总结
9.1promise做到异步操作和结果的分离
这一点是promise存在的最大价值体现。它的实现其实就是让then方法传入的函数在异步操作后执行即可。这里为了方便描述我只说成功的情况。
一种情况是在调用then方法的时候异步操作还未完成promise的状态是pedding。大致的流程如下
【这里本来是动图的但是csdn不允许上传这么大的动图具体的可以看我公众号吧嘻嘻我最近开始写公众号啦撒花】
另一种情况是在调用then方法的时候异步操作已经完成(promise的状态已经变成fulfilled)。这时候收集器就不用收集回调函数了而是可以直接执行大致的流程如下
【这里本来是动图的但是csdn不允许上传这么大的动图具体的可以看我公众号吧嘻嘻我最近开始写公众号啦撒花】
9.2then能链式调用是因为then返回是个新的promise
我们为了解决回调地狱使用promise时会进行链式调用。这是因为then方法返回一个新的promise。
并且这个新的promise倾向于返回最后的处理结果。
这句话怎么理解呢还是只说成功情况的处理。
1then不传入函数参数时透传promise的处理结果。
2then传入的函数参数没有返回时,也就是返回值为undefined,那么返回的新promise的value值是undefined。
3then传入的函数参数有返回值且返回值是promise,则取得这个promse最后的结果传递给它返回的promise。
4then传入的函数参数有返回值且返回值是thenable对象和promise差不多就是执行thenable对象的then方法取得这个promise最后的结果传递给它返回的promise。
5then传入的函数参数有返回值且返回值是正常数据则传递给它返回的promise。