一,背景
1.1,js的单线程
这一切,要从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)
//执行结果:132
setTimeout里面执行的就是异步操作的回调函数,等待完成后执行其中的代码。类似于地铁过闸机没网的那个兄弟先站到旁边处理下网络问题,解决了再排队进闸机,这样就不会堵着后续的人。
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())
a=1
})
}
let a=0
readFile()
printA()
从上文可以知道,a虽然被修改了,但它是在读文件操作(异步操作)中被修改的,因为js异步非阻塞的特性,在执行printA中的打印a时,它并没有执行,所以打印出来的a将是0。
那如何把a=1打印出来呢?
我们可以把一个函数作为参数,传给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())
a=1
callback()//这就是传进来的回调函数
})
}
let a=0
readFile(printA)
这就是回调函数:函数体在完成某种操作后由内向外调用某个外部函数,因为是在函数体内部调用,所以能由内向外获取到相关变量(作用域链)。
简单点说,就是把函数当作参数,传递给异步操作完成之后再执行。
1.4,js的事件循环机制
上文说到,js的异步非阻塞机制,那它是如何实现就一个主线程,完美运行所有的同步异步代码呢?
这就需要理解下js的事件循环机制,也就是人们所说的(Event Loop),可以用下图理解:
对于异步任务,我们会区分为宏任务和微任务。
常见的微任务:
Promises.(then catch finally),process.nextTick, MutationObserver
DOM渲染前触发
这里值得注意的是promise内是同步,而.then后才是微任务。
常见的宏任务:
整体代码script,setTimeout,setInterval ,setImmediate,I/O,UI 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 reader1=data.toString()
fs.readFile('test1.txt', (err,data) => {
let reader2=data.toString()
fs.readFile('test2.txt', (err,data) => {
let reader3=data.toString()
setTimeout(()=>{
let result=reader1+reader1+reader3
console.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)=>{
res1=res
return readFile('test1.txt')
})
.then((res)=>{
res2=res
return readFile('test2.txt')
})
.then((res)=>{
res3=res
console.log("结果",res1+res2+res3)
})
这样一来,就把异步的操作和结果通过.then进行了分离。避免了回调地狱的产生。
二,promise的then方法实现
那promise到底是个啥?为何能有如此效果呢?
2.1,promise的三种状态
pending: 一个promise在resolve或者reject前就处于这个状态。
fulfilled: 一个promise被resolve后就处于fulfilled状态,这个状态不能再改变,而且必须拥有一个不可变的值(value)。
rejected: 一个promise被reject后就处于rejected状态,这个状态也不能再改变,而且必须拥有一个不可变的拒绝原因(reason)。
2.2,promise.then方法
一个promise必须拥有一个then
方法来访问他的值或者拒绝原因。then
方法有两个参数:
promise.then(onFulfilled, onRejected)
并且这两个参数有以下特征:
1,onFulfilled 和 onRejected 都是可选参数。若不存在则忽略。
2,onFulfilled在promise结束前不可调用,结束后必须被调用,其第一个参数为 promise 的终值value,且调用次数不超过一次。
3,onRejected在被拒绝前不可被调用,拒绝执行后其必须被调用,其第一个参数为 promise 的原因reason,且调用册数不超过一次。
而这个then方法还支持以下特征:
1,then 方法必须返回一个 promise 对象。这样才支持多次then链式调用。
2,then 方法可以被同一个 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; // 初始状态为pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
//同步执行这个函数
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
exports.MyPromise=MyPromise
可以看到fn(resolve, reject)异步操作执行完毕后,会调用resolve和reject,于是promise里面就需要创建resolve和reject提供调用。
// 这两个方法直接写在构造函数里面
function MyPromise(fn) {
// ...省略前面代码...
// 存一下this,以便resolve和reject里面访问
var that = this;
// resolve方法参数是value
function resolve(value) {
if(that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
}
}
// reject方法参数是reason
function 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 aaa=p1.then((value)=>{
console.log(value)
})
},2000)
打印的结果:
注意到,这里我们是使用setTimeout包裹了then函数,这意味着现在只是将结果取到了then中进行处理,还不能等待异步完成后再执行then中的代码。
但是到这里,就可以大致知道promise到底是个啥?
可以把它当作一个异步操作的处理站点。
初始时状态是pending,当执行完毕异步操作(用户定义)后调用resolve/reject,修改状态变成fulfilled/rejected,并且把结果存储在value/reason。
然后then方法判断异步操作是否完成,完成的话,就取value/reason来完成用户接下来的操作(用户定义)。
其实还是利用的回调函数,把promsie当作中转站,将异步操作和结果隔离开来。(异步操作放在promise的入参函数中,结果放value/reason,then方法需要用到了再去这里取)。
2.4,then方法的回调函数收集与执行
实际上,我们使用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.5,then的入参和返回
2.5.1,then的入参
之前说过,then方法的入参不是函数则忽略,其实所谓“忽略”并不是什么都不干,对于onFulfilled
来说“忽略”就是将value
原封不动的返回,对于onRejected
来说就是返回reason
,onRejected
因为是错误分支,我们返回reason
应该throw一个Error,所以这里需要重写这两个入参:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 如果onFulfilled不是函数,给一个默认函数,返回value
var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value
// 如果onRejected不是函数,给一个默认函数,返回reason的Error
var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}
//...省略其他代码
}
2.5.2,then的返回得是promise
then
的返回值必须是一个promise(这里使用promise2)。且如果onFulfilled或者onRejected抛出一个异常e,则这个新的promise2必须拒绝执行,并且返回原因e。也就是把onFulfilled和onRejected用try…catch包裹一层。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 如果onFulfilled不是函数,给一个默认函数,返回value
var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value
// // 如果onRejected不是函数,给一个默认函数,返回reason的Error
var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}
var that=this
var promise2 = new MyPromise(function(resolve, reject) {
//如果异步已经成功则直接执行回调函数,期间有报错,需要reject
if(that.status === FULFILLED) {
try {
realOnFulfilled(that.value);
} catch (error) {
reject(error);
}
}
//如果异步已经失败则直接执行回调函数,期间有报错,需要reject
if(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.3,then的入参默认是函数
而如果onFulfilled不是函数且promise1成功执行,那么promise2必须成功执行并且返回相同的值。这时候,就需要直接透传resolve(this.value)给promise2了。
如果 onRejected
不是函数且 promise1
拒绝执行, promise2
必须拒绝执行并返回相同的原因。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 如果onFulfilled不是函数,给一个默认函数,返回value
var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value
// // 如果onRejected不是函数,给一个默认函数,返回reason的Error
var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}
var that=this
//如果异步已经成功则直接执行回调函数,期间有报错,需要reject
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
//then方法传入非函数,且成功执行,则把结果传给promise2
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
realOnFulfilled(that.value);
resolve(that.value);
}
} catch (error) {
reject(error);
}
});
return promise2;
}
//如果异步已经失败则直接执行回调函数,期间有报错,需要reject
if(this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
//then方法传入非函数,且成功执行,则把结果传给promise2
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
realOnRejected(that.reason);
//promise1的onRejected执行成功后,promise2应该被resolve,这样promise2的状态才会是fulfilled
resolve();
}
} 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 {
//如果传入的不是函数,则透传结果给promise2
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
realOnFulfilled(that.value);
resolve(that.value);
}
} catch (error) {
reject(error);
}
});
that.onRejectedCallbacks.push(function() {
try {
//如果传入的不是函数,则透传结果给promise2
if (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不是函数,给一个默认函数,返回value
var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value
// // 如果onRejected不是函数,给一个默认函数,返回reason的Error
var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}
var that=this
var promise2 = new MyPromise(function(resolve, reject) {
//如果异步已经成功则直接执行回调函数,期间有报错,需要reject
if(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);
}
}
//如果异步已经失败则直接执行回调函数,期间有报错,需要reject
if(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不是函数,给一个默认函数,返回value
var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value
// // 如果onRejected不是函数,给一个默认函数,返回reason的Error
var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}
var that=this
var promise2 = new MyPromise(function(resolve, reject) {
//如果异步已经成功则直接执行回调函数,期间有报错,需要reject
if(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)
}
//如果异步已经失败则直接执行回调函数,期间有报错,需要reject
if(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.6,Promise 解决过程
从上文看,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作为原因拒绝(reject)promise。
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,还要继续解析y
x.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.then
2.3.3.2 如果取回的x.then属性的结果为一个异常e,用e作为原因reject promise
2.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拒绝(reject)promise
2.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拒绝(reject)promise
2.3.3.4 如果then不是一个函数,用x完成(fulfill)promise
这里规范中讲得很复杂,其实就是为了处理返回的是类promsie(thenable)对象。
如下代码:
var thenableA = {
name: 'thenableA',
then: function (resolve, reject) {
console.log(`I'am ${this.name}`);
resolve(this.name)
}
}
var p1=new MyPromise((resolve, reject) => {
console.log('create promise1');
resolve(1)
})
p1.then((res) => {
return thenableA
})
打印出来就是:
create promise1
I'am 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 为参数执行 promise
if (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(`I'am ${this.name}`);
resolve(this.name)
}
}
var p1=new MyPromise((resolve, reject) => {
console.log('create promise1');
resolve(1)
})
var p2=p1.then((res) => {
return thenableA
})
setTimeout(()=>{
console.log("+++++",p2)
},0)
执行之后打印的是:
create promise1
I'am thenableA
+++++ MyPromise {
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; // 初始状态为pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
// 构造函数里面添加两个数组存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 存一下this,以便resolve和reject里面访问
var that = this;
// resolve方法参数是value
function resolve(value) {
if(that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
// resolve里面将所有成功的回调拿出来执行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}
// reject方法参数是reason
function 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不是函数,给一个默认函数,返回value
var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value
// // 如果onRejected不是函数,给一个默认函数,返回reason的Error
var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}
var that=this
var promise2 = new MyPromise(function(resolve, reject) {
//如果异步已经成功则直接执行回调函数,期间有报错,需要reject
if(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)
}
//如果异步已经失败则直接执行回调函数,期间有报错,需要reject
if(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,还要继续解析y
x.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 为参数执行 promise
if (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.js(test.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 p2=MyPromise.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 result=MyPromise.all([p1,p2,p3])
setTimeout(()=>{
console.log(result)//打印返回的所有结果
},1000)
result.then((res)=>{
console.log(res)
})
得到的结果:
七,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是因为它无法return
MyPromise.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 result=MyPromise.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.1,promise做到异步操作和结果的分离
这一点是promise存在的最大价值体现。它的实现,其实就是让then方法传入的函数在异步操作后执行即可。这里为了方便描述,我只说成功的情况。
一种情况是在调用then方法的时候,异步操作还未完成(promise的状态是pedding)。这时候是把then的传入参数添加到收集器中,先不执行,等异步操作完成,resolve时,遍历收集器时执行。大致的流程如下:
另一种情况是,在调用then方法的时候,异步操作已经完成(promise的状态已经变成fulfilled)。这时候收集器就不用收集回调函数了,而是可以直接执行,大致的流程如下:
9.2,then能链式调用是因为then返回是个新的promise
我们为了解决回调地狱,使用promise时,会进行链式调用。这是因为then方法返回一个新的promise。
并且这个新的promise倾向于返回最后的处理结果。
这句话怎么理解呢?还是只说成功情况的处理。
1,then不传入函数参数时,透传promise的处理结果。
2,then传入的函数参数没有返回时,也就是返回值为undefined,那么返回的新promise的value值是undefined。
3,then传入的函数参数有返回值,且返回值是promise,则取得这个promse最后的结果,传递给它返回的promise。
4,then传入的函数参数有返回值,且返回值是thenable对象,和promise差不多,就是执行thenable对象的then方法,取得这个promise最后的结果,传递给它返回的promise。
5,then传入的函数参数有返回值,且返回值是正常数据,则传递给它返回的promise。
原文链接:https://juejin.cn/post/7218178695679410231 作者:凌旭