从异步到promise

  • Post author:
  • Post category:其他




一,背景



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)
})

得到的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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是因为它无法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)。大致的流程如下:

【这里本来是动图的,但是csdn不允许上传这么大的动图,,,具体的可以看我公众号吧,嘻嘻,我最近开始写公众号啦!撒花!】

另一种情况是,在调用then方法的时候,异步操作已经完成(promise的状态已经变成fulfilled)。这时候收集器就不用收集回调函数了,而是可以直接执行,大致的流程如下:

【这里本来是动图的,但是csdn不允许上传这么大的动图,,,具体的可以看我公众号吧,嘻嘻,我最近开始写公众号啦!撒花!】



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。



版权声明:本文为weixin_42349568原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。