前言:你们好,我叫邵威儒,你们都喜欢喊我小邵,学的金融专业却凭借兴趣爱好入了程序猿的坑,从大学买的第一本vb和自学vb,我就与编程结下不解之缘,随后自学易语言写游戏辅助、交易软件,至今进入了前端领域,看到很多朋友都写文章分享,本身也弄一个玩玩,如下文章纯属我的理解,便于记录学习,确定有理解错误或理解不到位的地方,意在站在前辈的肩膀,分享我的对技术的通俗理解,共同成长!javascript
后续我会陆陆续续更新javascript方面,尽可能把javascript这个学习路径体系都写一下
包括前端所经常使用的es六、angular、react、vue、nodejs、koa、express、公众号等等
都会从浅到深,从入门开始逐步写,但愿能让你们有所收获,也但愿你们关注我~前端
源码地址:github.com/iamswr/prom…
文章列表:juejin.im/user/5a84f8…vue
Author: 邵威儒
Email: 166661688@qq.com
Wechat: 166661688
github: github.com/iamswr/java
JavaScript做为单线程语言,其特色也是其缺陷,特色就是不用处理多线程引起的占用资源、冲突啪啦啪啦等,缺陷就是同一时间,只能作一件事情,那么会存在一个问题,网络传输是有延迟的,好比A发一条信息到B服务器,在B服务器还没返回信息给A时,那么A就会一直在等待接收信息,会形成页面的假死,那么该怎么办?俗话说得好,程序猿改变世界,因而乎出现了异步的概念,我会分如下几点,去讲述我对前端异步的理解:node
回调函数被认为是一种高级函数,一种被做为参数传递给另外一个函数(在这称做"otherFunction")的高级函数,回调函数会在otherFunction内被调用(或执行)。回调函数的本质是一种模式(一种解决常见问题的模式),所以回调函数也被称为回调模式。react
是否是看起来一头懵逼,到底什么是回调函数?有什么做用?我对回调函数理解,从真正意义上让我忽然间恍然大悟的,就是当初研究jQuery底层源码的时候,咱们看如下一段代码:jquery
<img src='../a.jpg'></img>
<img src='../b.jpg'></img>
<img src='../c.jpg'></img>
$("img").attr("title",function(index,attr){
console.log(index) // 依次返回0 1 2
});
复制代码
在attr方法的第二个参数,传入了一个function,而该函数,会依次获取$('img')
的DOM对象对应index和attr,咱们能够在该function里,写咱们须要的业务逻辑,那么这样有什么好处呢?git
个人理解是,假如我要封装一个库,造一个轮子,那么要考虑到通用性和复用性而且提供一个途径,让使用者任意发挥想象写业务逻辑,而且把相关可能使用到的参数,都传给使用者。es6
假设,咱们如今有一个需求,要写一个判断类型的方法,常见的方法有如下几种:github
- typeOf // 简单的数据类型判断,栈区
- instanceof // 复杂的数据类型,堆区
- constructor // 复杂的数据类型,主要是用在继承的改写指向的构造函数,不多用于判断类型
- Object.prototype.toString.call() // 绝大多数库底层都是使用该方式,返回值如[object String]
复制代码
首先,咱们写一个isType方法
function isType(content,type,fn){
// 类型判断
let t = Object.prototype.toString
.call(content)
.replace(/\[object\s|\]/g,'')
// 判断完成后,执行传入的callback函数
fn(type,t)
}
复制代码
如今咱们要判断一个值的类型,而后拿到这个类型,最终执行咱们须要作的事情
isType('hello swr','String',function(type,t){ // 做为参数传入的函数,接收isType函数内的fn中type和t这两个参数
console.log(type === t) // true
})
复制代码
那么问题就出现了,好比咱们使用node.js的时候,进行文件读取操做时,想获取的值是一种嵌套依赖关系时,会出现什么问题呢?
目录结构:
- iamswr
- A.txt
- B.txt
- C.txt
其中
A.txt文件里的内容为字符串B.txt
B.txt文件里的内容为字符串C.txt
C.txt文件里的内容为字符串'hello swr'
那么当咱们想获取到'hello swr',会遇到什么问题呢?请看下面的代码
let fs = require('fs')
fs.readFile('A.txt','utf8',function(err,data){ // 此时回调函数data值为'B.txt'
fs.readFile(data,'utf8',function(err,data){ // 此时回调函数data值为'C.txt'
fs.readFile(data,'utf8',function(err,data){
console.log(data) // 'hello swr'
})
})
})
复制代码
以上这个例子若是嵌套依赖层次更高一些,那代码变得十分难维护以及难阅读,咱们在企业开发当中,常常会遇到想获得的数据,是经过嵌套依赖的关系,最终才得到须要的数据,陷入了回调地狱,而es6中,promise解决了这个让前端头疼的问题,后面我会详细讲promise,下面咱们先了解一下闭包、高阶函数。
我我的理解,闭包其实是一种函数,因此闭包技术也是函数技术的一种;闭包能作的事情函数几乎都能作,闭包有最大的两个用处,一个是能够读取函数内部的变量,另外一个就是让这些变量的值始终保持在内存中。
在javascript中,若是一个对象不被引用了,那么这个对象会被GC回收,不然则一直保留在内存中,那么利用这个特色,配合闭包使用,有如下几个优势:封闭做用域、保存做用域、做用域链条。
不污染全局变量,当团队协做时,好比A大佬,封装了jQuery库,而jQuery库内是有大量变量,若是不使用闭包,则jQuery库内的变量会污染整个项目,甚至和其余团员的变量有冲突
外部没法获取闭包内的变量,封闭了做用域
(function(){
var str = 'hello swr'
console.log(str) // 'hello swr'
})()
console.log(str) // 报错
咱们用原生js来写代码的时候,会存在一个问题,
好比有5个button标签
var btns = document.getElementsByTagName('button');
for(var i=0; i< btns.length; i++){
var btn = btns[i];
btn.onclick = function () {
alert('点击了第' + i + '个按钮');
}
}
不管咱们点击哪一个button,都是弹出'点击了第5个按钮',
由于btn.onclick事件是异步触发的,当事件被触发时,
for循环早已经结束,此时变量I的值已是5,
全部onclick事件函数从内到外查找变量i时,查找到的值老是5。
能够经过封闭做用域把每次循环的i值都封闭起来,
当时间函数顺着做用域链从内到外查找变量i时,
会先找到被封闭在闭包环境中的i,
若是有5个按钮, 则i的值就是0,1,2,3,4
var btns = document.getElementsByTagName('button');
for(var i=0; i< btns.length; i++){
(function (i) {
var btn = btns[i];
btn.onclick = function () {
alert('点击了第' + i + '个按钮');
}
})(i);
}
复制代码
咱们知道,在es6以前,只有函数是有做用域的说法,在es6出现了,则有了块级做用域的说法,好比
(function person(){
var name = '邵威儒'
console.log(name) // '邵威儒'
})()
console.log(name) // 报错
复制代码
在函数外部,是访问不了内部的name,这就是做用域。 在es6出了一个新的概念,就是块级做用域
{
let name = '邵威儒'
console.log(name) // '邵威儒'
}
console.log(name) // 报错
复制代码
效果和闭包同样
函数嵌套函数,那么内部的那个函数将造成做用域闭包。简单的说,这种闭包可以达到的好处就是让指令可以绑定一些全局数据去运行,优势是全局数据隐藏化、 将数据绑定在指令上运行,让指令再也不依赖全局数据。
function plus(num){
++num
return function(){
console.log(num)
}
}
let toPlus = plus(5)
此时toPlus实际上为
function(){
console.log(num)
}
而这个num实际上就是plus函数内做用域的num,此时咱们没法从外部修改num,并且把plus函数内的数据隐藏化,将数据绑定在toPlus上运行。
复制代码
好比说,咱们实际开发中会遇到一个问题,就是某个函数,要等多个异步执行完毕后才执行,这种状况怎么作呢?
通常会想到如下这个办法
let fs = require('fs')
let arr = []
fs.readFile('./a.txt','utf8',function(err,data){
arr.push(data) // 假设data为'hello'
})
fs.readFile('./b.txt','utf8',function(err,data){
arr.push(data) // 假设data为'swr'
})
console.log(arr) // 咱们但愿打印出来是['hello','swr']或['swr','hello'],可是打印出来的倒是[]
这是为何呢?
是由于javascript执行原理,是先执行同步,再执行异步的,而fs.readFile方法属于异步方法,因此还没执行完毕,就已经执行了console.log(arr)了
复制代码
对于这种并不是依赖嵌套获取,咱们称为“同步”获取,此同步非异步同步的那个同步,特别是这种异步请求的数据,获取到的时间前后顺序不一样,那咱们该如何实现“同步”获取呢?
let fs = require('fs')
function after(times,callback){
let arr = []
return function(data){
arr.push(data)
if(--times === 0){
callback(arr)
}
}
}
let fn = after(2,function(arr){
console.log(arr) // 当fn执行两次后,则会执行该回调函数
})
fs.readFile('./a.txt','utf8',function(err,data){
fn(data) // 假设data为'hello'
})
fs.readFile('./b.txt','uft8',function(err,data)=>{
fn(data) // 假设data为'swr'
})
最终当2个fs.readFile读取完毕后,执行了fn()达到2次时,则会打印出['hello','swr']或者['swr','hello']
复制代码
虽然以上的方式,实现了咱们须要的需求,可是问题来了,难道咱们每一次都要特地写一个after函数吗?其实还有一个概念,叫作发布订阅,订阅就相似你收藏了这个电台,而发布,则是这个电台向全部收藏了本电台的粉丝进行广播,看下面代码
let fs = require('fs')
let event = {
arr:[], // 存须要执行的函数
result:[], // 存结果
on(fn){ // 订阅
this.arr.push(fn)
},
emit(data){ // 发布
this.result.push(data)
this.arr.forEach(fn=>fn(this.result))
}
}
event.on(function(data){
if(data.length === 2){
console.log(data) // ['hello','swr'] 或者 ['swr','hello']
}
})
fs.readFile('./a.txt','utf8',(err,data)=>{
event.emit(data) // data为'hello'
})
fs.readFile('./b.txt','utf8',(err,data)=>{
event.emit(data) // data为'swr'
})
当两个fs.readFile读取完成,而且在其回调函数内执行了event.emit,最终会打印出['hello','swr'] 或者 ['swr','hello']
复制代码
啰啰嗦嗦说了那么多,主要是想你们了解一下回调函数以及闭包,由于这概念和promise的紧密关联的,promise部分我主要是想和你们根据promiseAplus规范,逐步手写一个promise的底层实现方式。
首先,promise怎么理解?我在知乎上看到一篇比较通俗易懂的小故事,你们能够看看,zhuanlan.zhihu.com/p/19622332
早上,老爸说:“儿子,天气如何?” 每周一早上,老爸问儿子下午的天气状况,儿子能够到自家房子旁边小山上使用望远镜来观看。儿子在出发时许诺(Promise)老爸(会通知老爸天气状况)。 此刻,老爸决定,若是天气不错,明天就出去捕鱼,不然就不去。并且若是儿子没法得到天气预报的话,也不去捕鱼。 30分钟左右,儿子回来了,每周的结局都不同。
结局A:成功得到了(retrieved)天气预报,晴天 :) 儿子成功获取了天气预报,天空晴朗,阳光明媚!承诺(Promise)兑现了(resolved),因而老爸决定开始为周日的捕鱼作准备。
结局B:一样成功得到了天气预报,雨天:( 儿子成功得到了天气预报,只不过是乌云密布,要下雨。承诺(Promise)兑现了(resolved),只是老爸决定呆在家里,由于天气很糟糕。
结局C:无法得到天气预报:-/ 出了问题,儿子无法得知天气预报,由于雾很大,就算站在小山上也没法看清。儿子没办法对象他离开时许下的诺言, promise was rejected!老爸决定留下来,这并不值得冒险。
首先咱们要了解PromiseA+规范 promisesaplus.com/
// Promise构造函数的第一个参数为executor
let promise = new Promise(function(resolve,reject){
console.log('我是会被当即执行的哟')
})
// promise的实例都有then方法
promise.then(()=>{ // 成功的回调
},()=>{ // 失败的回调
})
复制代码
// 默认时为pending态,既不会走成功的回调也不会走失败的回调
promise.then(()=>{
console.log('success1')
},()=>{
console.log('error1')
})
console.log('2')
在这段代码中,只会打印出'2',由于promise一直处于pending态,不会走then后的回调函数
复制代码
let promise = new Promise(function(resolve,reject){
console.log('1')
resolve() // 更改pending状态为resolved
})
promise.then(()=>{
console.log('success1')
},()=>{
console.log('error1')
})
console.log('2')
此时输出顺序为'1' -> '2' -> 'success1'
复制代码
let promise = new Promise(function(resolve,reject){
console.log('1')
setTimeout(()=>{ // 异步行为
resolve() // 更改状态为成功
},1000)
})
promise.then(()=>{
console.log("success1")
})
promise.then(()=>{
console.log('success2')
})
console.log("2")
此时输出顺序为'1' -> '2' -> 'success1' -> 'success2'
复制代码
let promise = new Promise(function(resolve,reject){
throw new Error('出错了') // 抛出错误
})
promise.then(()=>{
console.log('success1')
},()=>{
console.log('error1')
})
此时输出为 'error1'
复制代码
下面代码部分和源码实现部分要结合来看
// ----- 代码部分
// 1.executor默认在new的时候会自动执行
// 成功和失败的视乎能够传递参数
let promise = new Promise((resolve,reject)=>{ // 6.resolve、reject函数对应源码实现部分的resolve、reject函数
resolve('hello swr') // 11.执行resolve
})
// 7.Promise的实例都有then方法
promise.then((data)=>{ // 8.成功的回调函数
},(err)=>{ // 9.失败的回调函数
})
复制代码
// ----- 源码实现部分
// 2.声明一个Promise构造函数
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined // 12.由于value和reason值须要在Promise实例方法then中使用,因此把这两个值,赋给new出来的实例
function resolve(value){ // 3.声明一个resolve函数
self.value = value // 13.当调用了resolve而且传参数时,则把这value值赋予self.value
}
function reject(reason){ // 4.声明一个reject函数
self.reason = reason // 13.当调用了reject而且传参数时,则把这reason值赋予self.reason
}
executor(resolve,reject) // 5.把resolve、reject函数传到executor
}
// 由于Promise的实例都有then方法,那么意味着then方法是在Promise的原型对象中的方法
// 10.对应上面成功的回调函数onFulfilled以及失败的回调函数onRejected
Promise.prototype.then = function(onFulfilled,onRejected){
}
module.exports = Promise // 把Promise暴露出去
复制代码
此时,咱们会发现,如何去判断调用resolve仍是reject呢? 这个时候咱们在内部应该维护一个状态,而咱们以前说过了Promise有三种状态,分别为pending、resolved、rejected,那么咱们接着看下面的代码。
// ----- 代码部分
let promise = new Promise((resolve,reject)=>{
resolve('hello swr') // 5.暂时忽略此行
resolve('看看同时执行resolve和reject会发生什么?') // 5.此行执行resovle
reject('看看同时执行resolve和reject会发生什么?') // 5.此行执行reject
})
promise.then((data)=>{
console.log('success:' + data) // 5.当调用了resolve函数,则输出success:hello swr
},(err)=>{
})
复制代码
// ----- 源码实现部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending' // 1.在内部维护一个status状态
function resolve(value){
self.value = value
self.status = 'resolved' // 2.当调用了resolve时,更改状态为resolved
}
function reject(reason){
self.reason = reason
self.status = 'rejected' // 2.当调用了reject时,更改状态为rejected
}
executor(resolve,reject)
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
// 3.当咱们在then中,执行了成功或者失败的回调函数时,首先要判断目前处于什么状态
if(self.status === 'resolved'){
onFulfilled(self.value) // 4.当调用了resolve函数后,会执行成功的回调函数,而且把resolve中传递的值,传递给成功的回调函数
}
if(self.status === 'rejected'){
onRejected(self.reason) // 4.当调用了reject函数后,会执行成功的回调函数,而且把reject中传递的值,传递给失败的回调函数
}
}
module.exports = Promise
复制代码
当咱们在上面5中同时执行resolve和reject,会发现都可以执行,那么就违背了状态只能更改一次的原则了,下面咱们来解决这个问题。
// ----- 代码部分
let promise = new Promise((resolve,reject)=>{
resolve('看看同时执行resolve和reject会发生什么?') // 1. 此时执行resolve和reject
reject('看看同时执行resolve和reject会发生什么?') // 3.此时即便调用reject,由于resolve已经调用了一次,从pending更改成resolve,因此在第一次调用后,屡次调用也不会生效
// 4.以上resolve、reject暂时忽略掉,咱们考虑一个状况,当promise抛出错误时,怎么去处理呢?
throw new Error('出错啦')
})
promise.then((data)=>{
console.log('success:' + data)
},(err)=>{
})
复制代码
// ----- 源码实现部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
function resolve(value){
if(self.status === 'pending'){ // 2.此时新增一个状态判断,当状态为pending的时候才能执行
self.value = value
self.status = 'resolved'
}
}
function reject(reason){
if(self.status === 'pending'){ // 2.此时新增一个状态判断,当状态为pending的时候才能执行
self.reason = reason
self.status = 'rejected'
}
}
// 5.当咱们在执行executor时,内部抛出错误的时候,能够利用try catch来处理这个问题
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
if(self.status === 'resolved'){
onFulfilled(self.value)
}
if(self.status === 'rejected'){
onRejected(self.reason)
}
}
module.exports = Promise
复制代码
这样咱们就解决了屡次调用,只认第一次的更改状态,而且当抛出错误时,使用try catch来处理,那么接下来,咱们想一下,目前咱们都是new一个Promise,而后调用then,这整个流程,仿佛没任何问题,可是,如今问题出现了,若是此时resolve或者reject是处于setTimeout(()=>{resolve()},3000)中,即处于异步中,当咱们new一个Promise时,不会立刻执行异步代码,而是直接执行了promise.then这个函数,而此时由于self.status的状态依然是处于pending,因此不会执行resolve或者reject,当同步代码执行完毕后,执行异步代码时,更改了状态为resolved或者rejected时,此时then方法已经执行完毕了,不会再次执行then的方法,那么此时咱们该如何处理?
还存在一个问题,就是上面所说的,同一个promise的实例能够then屡次,成功时会调用全部的成功方法,失败时会调用全部的失败方法,那这个又该如何处理呢?
能够利用咱们前面所说的发布订阅的思路来解决,如今咱们看下面代码。
// ----- 代码部分
let promise = new Promise((resolve,reject)=>{
setTimeout(()=>{ // 1.此时resolve处于异步
resolve('hello swr')
},3000)
})
promise.then((data)=>{ // 多个then
console.log('success1:' + data)
},(err)=>{
})
promise.then((data)=>{ // 多个then
console.log('success2:' + data)
},(err)=>{
})
复制代码
// ----- 源码实现部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResolvedCallbacks = [] // 2.可能new Promise中会有异步的操做,此时咱们把异步操做时,执行的then函数的成功回调,统一保存在该数组中
self.onRejectedCallbacks = [] // 2.可能new Promise中会有异步的操做,此时咱们把异步操做时,执行的then函数的失败回调,统一保存在该数组中
function resolve(value){
if(self.status === 'pending'){
self.value = value
self.status = 'resolved'
// 4.当调用resolve时,把该数组中存放的成功回调都执行一遍,若是是异步,则会把成功的回调都存到该数组里了,若是是异步,则没存到。
self.onResolvedCallbacks.forEach(fn=>fn())
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected'
// 4.当调用reject时,把该数组中存放的失败回调都执行一遍,若是是异步,则会把成功的回调都存到该数组里了,若是是异步,则没存到。
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
if(self.status === 'resolved'){
onFulfilled(self.value)
}
if(self.status === 'rejected'){
onRejected(self.reason)
}
// 3.当new Promise中有resolve、reject处于异步中,执行then的时候,状态为pending,
if(self.status === 'pending'){
self.onResolvedCallbacks.push(()=>{
onFulfilled(self.value)
}) // 3. 把成功的回调函数,存到该数组中,这样写的好处,就是把参数传进去,不须要未来遍历onResolvedCallbacks时,再传参
self.onRejectedCallbacks.push(()=>{
onRejected(self.reason)
}) // 3. 把失败的回调函数,存到该数组中,这样写的好处,就是把参数传进去,不须要未来遍历onRejectedCallbacks时,再传参
}
}
module.exports = Promise
复制代码
到此为止,咱们简版的Promise实现得差很少了,小伙伴们能够对着代码敲一下,感觉一下,体会一下。
其实Promise的核心在于链式调用,Promise主要是解决2个问题:
首先,好比回调地狱怎么解决呢?那么咱们来看下面的代码,而且改成promise。
// 回调函数
let fs = require('fs')
fs.readFile('./a.txt','utf8',(err,data)=>{ // 往fs.readFile方法传递了第三个为函数的参数
if(err){
console.log(err)
return
}
console.log(data)
})
// 改写为Promise
let fs = require('fs')
function read(filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err)
resolve()
})
})
}
read('./a.txt','utf8').then((data)=>{ // 在这里则再也不须要传回调函数进去,而是采用then来达到链式调用
console.log(data)
},(err)=>{
console.log(err)
})
// 这样看好像Promise也没什么优点,那么接下来咱们对比一下
// 假设有3个文件
// - 1.txt 文本内容为'2.txt'
// - 2.txt 文本内容为'3.txt'
// - 3.txt 文本内容为'hello swr'
// 用回调函数
fs.readFile('./1.txt','utf8',(err,data)=>{
fs.readFile(data,'utf8',(err,data)=>{
fs.readFile(data,'utf8',(err,data)=>{
console.log(data) // hello swr
})
})
})
// 用Promise
read('./1.txt','utf8')
.then((data)=>{
// 1.若是一个promise执行完后,返回的仍是一个promise,
// 会把这个promise的执行结果会传递给下一次then中
return read(data,'utf8')
})
.then((data)=>{
return read(data,'utf8')
})
.then((data)=>{
// 2.若是在then中返回的不是一个promise,
// 而是一个普通值,会将这个普通值做为下次then的成功的结果
return data.split('').reverse().join('')
})
.then((data)=>{
console.log(data) // rws olleh
// 3.若是当前then中失败了,会走下一个then的失败回调
throw new Error('出错')
})
.then(null,(err)=>{
console.log(err) // Error:出错 报错了
// 4.若是在then中不返回值,虽然没有显式返回,
// 可是默认是返回undefined,是属于普通值,依然会把这个普通值传到
// 下一个then的成功回调中
})
.then((data)=>{
console.log(data) // undefined
})
复制代码
从上面能够看得出,改写为Promise的代码,更好阅读和维护,从用Promise方式能够得出结论:
// 若是在then中抛出错误,会怎样呢?
// 情景一,会被下一个then中的失败回调捕获
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出错了')
})
.then(null,(err)=>{
console.log(err) // Error:出错了 报错
})
// 情景二,若是没有被失败的回调捕获,抛出错误最终会变成异常
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出错了')
})
// 情景三,若是没有被失败的回调捕获,那么最终会被catch捕获到
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出错了')
})
.then((data)=>{
})
.catch((err)=>{
console.log(err) // Error:出错了 报错
})
// 情景四,若是被失败的回调捕获了,那么不会被catch捕获到
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出错了')
})
.then(null,(err)=>{
console.log(err) // Error:出错了 报错
})
.catch((err)=>{
console.log(err) // 不会执行到这里
})
复制代码
jquery的链式调用,是经过其内部执行完后return this,返回自身这个对象,达到链式调用的目的,那为何Promise不采用这种方式呢?
咱们能够看如下代码,感觉一下。
let promise = new Promise((resolve,reject)=>{
resolve() // 执行resolve,使状态从pending变为resolved
})
let promise2 = promise.then(()=>{
throw new Error() // 抛出错误
return this // 返回自身
})
// 那么我在promise2中,调then,那么它会执行失败的回调吗?答案是不会的。
// 由于咱们不可能让状态既成功又失败的
// promise成功了,若是返回this,那不能走向失败
promise2.then(()=>{
console.log('来到这里了')
},()=>{
console.log('会来到这里吗?')
})
// 此时then中返回自身后,promise2其实就是promise,而咱们想达到
// 的是把当前的then返回后,传到下一个then中,可是咱们这样返回this,
// 其实会变得很矛盾,由于状态已经从pending变为resolved,不可能又从resolved变成rejected的
// 因此得出结论,返回的必须是一个新的promise,由于promise成功后不能再走失败
// 只能建立一个新的promise再执行业务逻辑,返回同一个promise的话,就不能既成功又失败
复制代码
// ----- 代码部分
let promise = new Promise((resolve,reject)=>{
resolve()
})
// 2.返回的值为promise2 为何这样规定呢?这是promiseA+规范规定的,咱们要遵循
let promise2 = promise.then((data)=>{
return x // 1.then中的返回值x多是普通值也多是promise,而且传给下一个then
}).then((data)=>{
console.log(data) // x的值
})
复制代码
// ----- 源码实现部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResolvedCallbacks = []
self.onRejectedCallbacks = []
function resolve(value){
if(self.status === 'pending'){
self.value = value
self.status = 'resolved'
self.onResolvedCallbacks.forEach(fn=>fn())
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected'
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
let promise2 // 3.上面讲promise链式调用时,已经说了返回的是一个新的promise对象,那么咱们声明一个新的promise
// 4.那么咱们new一个新的promise,而且把如下代码放到promise中
let promise2 = new Promise((resolve,reject)=>{
if(self.status === 'resolved'){
// 7.当执行成功回调的时候,可能会出现异常,那么就把这个异常做为promise2的错误的结果
try{
let x = onFulfilled(self.value) // 6.这里的x,就是上面then中执行完返回的结果,咱们在这里声明一个x用来接收
// 8.根据promiseA+规范,咱们应该提供一个函数来处理promise2
// 我我的的理解是,then中无论是成功回调仍是失败回调,其返回
// 值,有多是promise,也有多是普通值,也有多是抛出错误
// 那么咱们就须要一个函数来处理这几种不一样的状况
// 这个函数咱们声明为resolvePromise吧
resolvePromise(promise2,x,resolve,reject)
// 9. 这里的promise2就是当前的promise2,x则是执行then中成功回调后返回的结果,若是是成功则调promise2的resolve,失败则调reject
}catch(e){
reject(e) // 注意:这里的reject是这个promise2的reject
}
}
if(self.status === 'rejected'){
// 同6-7步
try{
let x = onRejected(self.reason)
// 同8-9
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
}
if(self.status === 'pending'){
self.onResolvedCallbacks.push(()=>{
// 同6-7步
try{
let x = onFulfilled(self.value)
// 同8-9
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
self.onRejectedCallbacks.push(()=>{
// 同6-7步
try{
let x = onRejected(self.reason)
// 同8-9
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
}
})
return promise2 // 5.在jquery中是return this,可是在promise中,则是返回一个新的promise对象
}
module.exports = Promise
复制代码
接下来咱们写一下resolvePromise这个函数,整个Promise最核心的部分就是在这里
// ----- 代码部分
let promise = new Promise((resolve,reject)=>{
resolve()
})
let promise2 = promise.then((data)=>{
return x
}).then((data)=>{
console.log(data)
})
// 2.咱们在resolvePromise函数中,在原生状况下,若是传参的时候,promise2和x是同一个对象会发生什么呢?
let promise = new Promise((resolve,reject)=>{
resolve()
})
let promise2 = promise.then(()=>{
return promise2
// 2.1报错 UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>
// 报错的意思是,陷入了死循环,那怎么理解呢?
// promise2的成功或失败是要取决于promise中then的返回结果,而返回的倒是promi2本身
// 这样就陷入死循环了,promise2是依赖于promise的then返回的结果,
// 而then返回的结果是promise2,而then中的promise2,既不是成功也不是失败,不能本身等于本身
})
// 7.当取一个对象上的属性,可能存在报异常的状况,怎么理解呢?
// 由于这个方法有可能不是本身写的,可能别人搞恶做剧乱写的,看如下代码。
let obj = {}
// 给obj对象定义一个then方法,当咱们去obj对象中调用then方法时
// 就会执行里面的get,而get则是抛出异常
Object.defineProperty(obj,'then',{
get(){
throw new Error()
}
})
// 10.为何要用call呢?解决了什么问题?看一下如下代码
首先咱们执行
promise.then(()=>{
console.log(this) // 此时this是指向该promise的,对象的方法中this是指向这个对象的
})
可是咱们在下面经过let then = promise.then,来判断是否promise,是否会异常
当咱们执行then时,里面的this仍是会指向这个promise吗?答案是不必定的,
由于此时then,若是在全局下执行,指向的可能就是window了,因此为了让this的
指向正确,咱们须要经过
then.call(promise),来把then的this指向promise
复制代码
// ----- 源码实现部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResolvedCallbacks = []
self.onRejectedCallbacks = []
function resolve(value){
if(self.status === 'pending'){
self.value = value
self.status = 'resolved'
self.onResolvedCallbacks.forEach(fn=>fn())
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected'
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
// 1.声明一个resolvePromise函数
// 这个函数很是核心,全部的promise都遵循这个规范,全部的promise能够通用,
/**
*
* @param {*} promise2 then的返回值,返回新的promise
* @param {*} x then中成功函数或者失败函数的返回值
* @param {*} resolve promise2的resolve
* @param {*} reject promise2的reject
*/
function resolvePromise(promise2,x,resolve,reject){
// 3.从2中咱们能够得出,本身不能等于本身
// 当promise2和x是同一个对象的时候,则走reject
if(promise2 === x){
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 4.由于then中的返回值能够为promise,当x为对象或者函数,才有可能返回的是promise
let called
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
// 8.从第7步,能够看出为何会存在抛出异常的可能,因此使用try catch处理
try{
// 6.由于当x为promise的话,是存在then方法的
// 可是咱们取一个对象上的属性,也有可能出现异常,咱们能够看一下第7步
let then = x.then
// 9.咱们为何在这里用call呢?解决了什么问题呢?能够看上面的第10步
// x可能仍是个promise,那么就让这个promise执行
// 可是仍是存在一个恶做剧的状况,就是{then:{}}
// 此时须要新增一个判断then是否函数
if(typeof === 'function'){
then.call(x,(y)=>{ // y是返回promise后的成功结果
// 一开始咱们在这里写的是resolve(y),可是考虑到一点
// 这个y,有可能仍是一个promise,
// 也就是说resolve(new Promise(...))
// 因此涉及到递归,咱们把resolve(y)改为如下
// 12.限制既调resolve,也调reject
if(called) return
called = true
resolvePromise(promise2,y,resolve,reject)
// 这样的话,代码会一直递归,取到最后一层promise
// 11.这里有一种状况,就是不能既调成功也调失败,只能挑一次,
// 可是咱们前面不是处理过这个状况了吗?
// 理论上是这样的,可是咱们前面也说了,resolvePromise这个函数
// 是全部promise通用的,也能够是别人写的promise,若是别人
// 的promise可能既会调resolve也会调reject,那么就会出问题了,因此咱们接下来要
// 作一下限制,这个咱们写在第12步
},(err)=>{ // err是返回promise后的失败结果
if(called) return
called = true
reject(err)
})
}else{
resolve(x) // 若是then不是函数的话,那么则是普通对象,直接走resolve成功
}
}catch(e){ // 当出现异常则直接走reject失败
if(called) return
called = true
reject(e)
}
}else{ // 5.x为一个常量,则是走resolve成功
resolve(x)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
// onFulfilled、onRejected是可选参数
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
let self = this
let promise2
let promise2 = new Promise((resolve,reject)=>{
if(self.status === 'resolved'){
// 13.根据promiseA+规范,onFulfilled或onRejected必须
// 被调用不是当前的上下文,then方法是异步的
setTimeout(()=>{
try{
let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}
if(self.status === 'rejected'){
// 同13
setTimeout(()=>{
try{
let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}
if(self.status === 'pending'){
self.onResolvedCallbacks.push(()=>{
// 同13
setTimeout(()=>{
try{
let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
self.onRejectedCallbacks.push(()=>{
// 同13
setTimeout(()=>{
try{
let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
}
})
return promise2
}
// 14.到目前为止,根据promiseA+规范的代码写得差很少了,咱们能够经过测试代码来测试咱们是否写得正确,下面咱们写一段测试代码
Promise.defer = Promise.deferred = function(){
let dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// 14.接下来咱们要安装一个插件,npm install promises-aplus-test -g
module.exports = Promise
复制代码
// 完整代码 也顺便带你们理顺一下
function Promise(executor) {
let self = this;
self.value = undefined; // 成功的值
self.reason = undefined; // 失败的值
self.status = 'pending'; // 目前promise的状态pending
self.onResolvedCallbacks = []; // 可能new Promise的时候会存在异步操做,把成功和失败的回调保存起来
self.onRejectedCallbacks = [];
function resolve(value) { // 把状态更改成成功
if (self.status === 'pending') { // 只有在pending的状态才能转为成功态
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise时异步操做,存在的成功回调保存起来
}
}
function reject(reason) { // 把状态更改成失败
if (self.status === 'pending') { // 只有在pending的状态才能转为失败态
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise时异步操做,存在的失败回调保存起来
}
}
try {
// 在new Promise的时候,当即执行的函数,称为执行器
executor(resolve, reject);
} catch (e) { // 若是执行executor抛出错误,则会走失败reject
reject(e);
}
}
// 这个函数为核心,全部的promise都遵循这个规范
// 主要是处理then中返回的值x和promise2的关系
function resolvePromise(promise2,x,resolve,reject){
// 当promise2和then返回的值x为同一个对象时,变成了本身等本身,会陷入死循环
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;
if(typeof then === 'function'){
then.call(x,y=>{
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject);
},err=>{
if(called) return;
called = true;
reject(err);
});
}else{
resolve(x);
}
}catch(e){
if(called) return;
called = true;
reject(e);
}
}else{
resolve(x);
}
}
// then调用的时候,都是属于异步,是一个微任务
// 微任务会比宏任务先执行
// onFulfilled为成功的回调,onRejected为失败的回调
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;
// 上面讲了,promise和jquery的区别,promise不能单纯返回自身,
// 而是每次都是返回一个新的promise,才能够实现链式调用,
// 由于同一个promise的pending resolve reject只能更改一次
promise2 = new Promise((resolve, reject) => {
if (self.status === 'resolved') {
// 为何要加setTimeout?
// 首先是promiseA+规范要求的
// 其次是你们写的代码,有的是同步,有的是异步
// 因此为了更加统一,就使用为setTimeout变为异步了,保持一致性
setTimeout(()=>{
try { // 上面executor虽然使用try catch捕捉错误
// 可是在异步中,不必定可以捕捉,因此在这里
// 用try catch捕捉
let x = onFulfilled(self.value);
// 在then中,返回值多是一个promise,因此
// 须要resolvePromise对返回值进行判断
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.defer = Promise.deferred = function(){
let dfd = {};
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
module.exports = Promise;
复制代码
执行promises-aplus-tests promise.js
复制代码
到此为止,咱们已经写了一个符合promiseA+规范的promise了,你们能够好好多看几回。
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
Promise.prototype.catch = function(onRejected){
return this.then(null,onRejected);
};
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=>{
processData(i,data);
},reject);
}
})
}
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(resolve,reject);
}
})
}
复制代码
// 原生的Promise.resolve使用
Promise.resolve('hello swr').then((data)=>{ // 直接把成功的值传递给下一个then
console.log(data) // hello swr
})
// 那么Promise.resolve内部是怎么实现的呢?
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{ // 在内部new一个Promise对象
resolve(value)
})
}
// 同理,Promise.reject内部也是相似实现的
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason)
})
}
复制代码
// 原生Promise的catch使用
Promise.reject('hello swr').catch((e)=>{
console.log(e) // hello swr
})
// 上面这段代码至关于下面这段代码
Promise.reject('hello swr').then(null,(e)=>{ // then里直接走了失败的回调
console.log(e) // hello swr
})
// 内部实现
Promise.prototype.catch = function(onRejected){
return this.then(null,onRejected) // 至关于then里的成功回调只传个null
}
复制代码
// 原生Promise.all的使用
// 假设1.txt内容为hello 2.txt内容为swr
let fs = require('fs')
function read(filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err)
resolve(data)
})
})
}
Promise.all([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
console.log(data) // 所有读取成功后返回 ['hello','swr']
// 须要注意的是,当其中某个失败的话,则会走失败的回调函数
})
// 内部实现
Promise.all = function(promises){ // promises 是一个数组
return new Promise((resolve,reject)=>{
let arr = []
let i = 0
function processData(index,data){
arr[index] = data
// 5.咱们能用arr.length === promises.length来判断请求是否所有完成吗?
// 答案是不行的,假设arr[2] = 'hello swr'
// 那么打印这个arr,将是[empty × 2, "hello swr"],
// 此时数组长度也是为3,而数组arr[0] arr[1]则为空
// 那么换成如下的办法
if(++i === promises.length){ // 6.利用i自增来判断是否都成功执行
resolve(arr) // 此时arr 为['hello','swr']
}
}
for(let i = 0;i < promises.length;i++){ // 1.在此处遍历执行
promises[i].then((data)=>{ // 2.data是成功后返回的结果
processData(i,data) // 4.由于Promise.all最终返回的是一个数组成员按照顺序排序的数组
// 并且异步执行,返回并不必定按照顺序
// 因此须要传当前的i
},reject) // 3.若是其中有一个失败的话,则调用reject
}
})
}
复制代码
// 原生Promise.race的使用
// 一个成功就走成功的回调,一个失败就走失败的回调
Promise.race([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
console.log(data) // 可能返回 'hello' 也可能返回 'swr' 看哪一个返回快就用哪一个做为结果
})
// 内部实现
Promise.race = function(promises){ // promises 是一个数组
return new Promise((resolve,reject)=>{
for(let i = 0;i < promises.length;i++){
promises[i].then(resolve,reject) // 和上面Promise.all有点相似
}
})
}
复制代码
这个语法糖能够简化一些操做,好比
let fs = require('fs')
// 写法一:
function read(filePath,encoding){
// 这里的new Promise依然是传递了一个executor回调函数
// 咱们该怎样减小回调函数嵌套呢?
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err)
resolve(data)
})
})
}
// 写法二:
// 这样的写法减小了一层回调函数的嵌套
function read(filePath,encoding){
let dfd = Promise.defer()
fs.readFile(filePath,encoding,(err,data)=>{
if(err) dfd.reject(err)
dfd.resolve(data)
})
return dfd.promise
}
read('./1.txt','utf8').then((data)=>{
console.log(data)
})
复制代码
结尾:第一次写,都是想到哪写到哪,请你们多多谅解~ 也但愿对你们有所帮助,promise的源码实现,我最大的收获并非怎么实现promise,而是编程思惟,你们能够多多往深里想想,也但愿你们能够和我进行交流,共同进步