从异步到promise

一,背景

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),可以用下图理解:

从异步到promise
对于异步任务,我们会区分为宏任务和微任务。

常见的微任务:

Promises.(then catch finally),process.nextTick, MutationObserver
DOM渲染前触发

这里值得注意的是promise内是同步,而.then后才是微任务。

常见的宏任务:

整体代码script,setTimeoutsetInterval ,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)

打印的结果:

从异步到promise

注意到,这里我们是使用setTimeout包裹了then函数,这意味着现在只是将结果取到了then中进行处理,还不能等待异步完成后再执行then中的代码。

但是到这里,就可以大致知道promise到底是个啥?

可以把它当作一个异步操作的处理站点。

初始时状态是pending,当执行完毕异步操作(用户定义)后调用resolve/reject,修改状态变成fulfilled/rejected,并且把结果存储在value/reason。

然后then方法判断异步操作是否完成,完成的话,就取value/reason来完成用户接下来的操作(用户定义)。

其实还是利用的回调函数,把promsie当作中转站,将异步操作和结果隔离开来。(异步操作放在promise的入参函数中,结果放value/reason,then方法需要用到了再去这里取)。

从异步到promise

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最核心的异步操作过程与结果的分离。

从异步到promise

2.5,then的入参和返回

2.5.1,then的入参

之前说过,then方法的入参不是函数则忽略,其实所谓“忽略”并不是什么都不干,对于onFulfilled来说“忽略”就是将value原封不动的返回,对于onRejected来说就是返回reasononRejected因为是错误分支,我们返回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
}

在规范中还有一条:onFulfilledonRejected 只有在执行环境堆栈仅包含平台代码时才可被调用。这一条的意思是实践中要确保 onFulfilledonRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。所以在我们执行onFulfilledonRejected的时候都应该包到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

四,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 实例。它接受一个数组作为参数,p1p2p3都是 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

七,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时,遍历收集器时执行。大致的流程如下:
从异步到promise

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

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 作者:凌旭

(0)
上一篇 2023年4月5日 下午4:52
下一篇 2023年4月5日 下午5:03

相关推荐

发表回复

登录后才能评论