再谈Promise以及其实现-没有基于Promise/A规范

401 阅读5分钟

Promise的前世今生

在js中,异步是一个非常重要的组成部分,它基于事件循环,保证了优先级更高任务的优先执行权,比如js下载、UI渲染、js中非异步的任务,异步使得单进程的js能够做到非阻塞,这在node显得攸关重要,它使得js不必等待I/O操作返回结果,而能去处理其他任务。但是异步也存在着缺点,最明显的就是回地狱,而Pormise规范的出现,让开发者从回调地狱从解放出来。它由社区提出和实现,之后被es6加入到标准当中。

Promise的组成

Promise最基本的是由status、resolve、onResolved、reject、onRejected、then和catch几部分组成:

status:状态管理,有pendding,fulfilled和rejected三种状态,且状态一经改变是无法逆转的;
resolve: 一个函数,当调用该函数时,说明异步任务执行成功了,promise的status由pendding转为fulfilled,且会调用成功的回调函数onResolved;
reject: 一个函数,当调用该函数时,说明异步任务执行失败,promise的status由pendding转为rejected,且会调用失败的回调函数onRejected;
then: 一个函数,在then的参数里面,我们需要定义回调成功需要执行的成功回调函数,promise会将这个成功回调函数注册到onResolved,由resolve触发调用,并且还支持链式调用;
catch: 一个函数,在reject的参数里面,我们需要回调失败需要执行的失败回调函数,promise会将这个失败回调函数注册到onRejected,由reject触发调用;

image

备注: 这里没讲race和all方法等,有兴趣的可以去看下文档。

使用方法

通过new创建一个Promise对象,传入一个函数参数,这个函数包含resolve和reject参数,这两个参数,如上所述也是函数,当异步任务完成的时候,调用resolve方法,当异步任务失败的时候,调用reject方法。eg:

var p = new Promise(function(resolve, reject){
    setTimeout(function() {
        resolve(1)
    }, 1000)
})

then方法,参数也是一个回调函数,当你调用resolve方法后,这个回调函数就会调用,且resolve函数传入的参数,会带给这个回调函数的参数,eg:

p.then(function(arg){
    console.log(arg) // 1
})

catch方法,参数同样是一个回调函数,当你调用reject方法后,这个回调函数就会调用,且reject函数传入的参数,会带给这个回调函数,使用例子和then同理。
所以我们的Promise的调用可以是这样的:

var p = new Promise(function(resolve, reject){
    // 异步任务执行,且看结果调用resolve还是reject
}).then(function(){
    // do something1
    return new Promise...
}).then(function() {
    // do something2
    return new Promise....
}).then(function() {
    // do something3
    return new Promise....
}).then(function() {
    // do something4
    return new Promise....
})

对比用回调的方式

doAsyncTask1(function(){
    // do something1
    doAsyncTask2(function() {
        // do something2
        doAsyncTask3(function() {
            // do something3
            doAsyncTask4(function(){
                // do something4
            })
        })
    })
    
})

是不是比起异步编程,promise更人性化呢?

async await

上面promise的调用方式其实还不够优雅,还有更加优雅的调用方式,那就是async await方式,我们来看下如何用。

async testPromise() {
   var result = await new Promise(function(resolve, reject){
       setTimeout(function(){
           resolve(1)
       }, 2000)
   })
   console.log(result)
   var result1 = await new Promise(function(resolve, reject){
       setTimeout(function(){
           resolve(1)
       }, 2000)
   })
   console.log(result1)
}

是不是已经变成了我们同步的编程方式了?
这里有两点需要注意的:

  1. await关键字必须在使用async修饰的方法中才能使用,反之则没问题。
  2. async不能直接使用,需要使用babel进行转译,一般需要借助webpack进行配置,不能单独在js中使用,这个相对来说比较麻烦。

如何实现自己实现Promise

面试官很喜欢问你如何实现一个Promise,这个时候我们就需要对Promise有一定的理解,我们不去看Promise/A+规范,太复杂晦涩难懂了,我们就按照上面讲的Promise的组成来实现一个简单的Promise。

function _Promise(fn) {
    this.status = 'pending';
    this.onResolved = null;
    this.onRejected = null;
    
    fn.call(this, this.resolve.bind(this), this.reject.bind(this))
    
}

_Promise.prototype.resolve = function(arg) {
    if(this.status === 'pending') {
        this.onResolved(arg)
        this.status = 'fulfilled'
    }
}
_Promise.prototype.reject = function(arg) {
    if(this.status === 'pending') {
        this.onRejected(arg)
        this.status = 'rejected'
    }
}

_Promise.prototype.then = function(onResolved) {
    this.onResolved = onResolved
}

_Promise.prototype.reject = function(onRejected) {
    this.onResolved = onRejected
}

这就是一个最简单的Promise实现方式,但是它还不支持链式调用。所以我们需要在then方法再返回一个Promise, 我们改造一下:

function _Promise(fn) {
    this.status = 'pending';
    this.onResolved = null;
    this.onRejected = null;
    this.childReject = null;
    this.childResolve = null;
    
    fn.call(this, this.resolve.bind(this), this.reject.bind(this))
    
}

_Promise.prototype.resolve = function(arg) {
    if(this.status === 'pending') {
        if(this.onResolved) {
            var ret = this.onResolved(arg)
            // 如果第二个then return的是一个用户自定义的promise,我们称为PromiseB,则需要把childResolve赋值给这个PromiseB的onResolved,交给这个PromiseB来执行
            if(ret instanceof _Promise) {
                ret.onResolved = this.childResolve
                return
            }
            // 否则直接调用childResove确保第二个then的回调执行
            this.childResolve && this.childResolve()
        }
        this.status = 'fulfilled'
    }
    
}
_Promise.prototype.reject = function(arg) {
    if(this.status === 'pending') {
        this.onRejected && this.onRejected(arg)
        this.childReject && this.childReject()
        this.status = 'rejected'
    }
}

_Promise.prototype.then = function(onResolved) {
    this.onResolved = onResolved
    var that = this
    // 定义一个promise,我们简称PromiseA,以便链式调用
    return new _Promise(function(resolve, reject){
      // 这里需要确保resolve方法在第一个promise的resolve调用后调用,那么需要把它保存到第一个promise里面
      that.childResolve = resolve
      that.childReject = reject
    })
}

_Promise.prototype.reject = function(onRejected) {
    this.onResolved = onRejected
}

// 例子
new _Promise((resolve) => {
    setTimeout(() => {
        resolve()
    }, 2000)
}).then(() => {
    console.log('promise success')
    return new _promise((resolve) => {
        setTimeout(() => {
            resolve()
        }, 1000)
    })
}).then(() => {
    console.log('promise2 success')
})

这个实现,有三个Promise,第一个是首个Promise,第二个PormiseA即使如果then方法没有返回用户自定义的Promise的时候,我们方便链式调用的Promise,第三个PromiseB是then方法返回用户自定义的Promise的时候,我们需要把第二个then的回调交还给PromiseB执行。