从Promise基本用法,实现手写Promise

2,429 阅读4分钟

Promise

Promise是异步编程的⼀种解决⽅案。

Promise对象有以下两个特点:

  1. 对象的状态不受外界影响。
    Promise对象代表一个异步操作,有3种状态:Pending、Fulfilled和Rejected。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  2. ⼀旦状态改变就不会再变,任何时候都可以得到这个结果。
    Promise对象的状态改变只有两种可能:从Pending变为Fulfilled和从Pending变为Rejected。只要这两种情况发⽣,状态就凝固了,不会再 变,⽽是⼀直保持这个结果,这时就称为Resolved(已定型)。

基本用法

// 创建一个promise对象
var promise = new Promise(function(resolve, reject) {
    ... 
    if(/*异步操作成功*/){
        resolve(data);
    }else {
        reject(err)
    }
)

Promise构造函数接受⼀个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不⽤⾃⼰部署。

  • resolve函数的作⽤是:Pending-->Resolved,成功时调⽤,并将异步操作的结果作为参数传递出去;
  • reject函数的作⽤是,Pending-->Rejected,失败时调⽤,并将异步操作报出的错误作为参数传递出去。

Promise实例⽣成以后,可以⽤then⽅法分别指定Resolved状态和Rejected状态的回调函数。

promise.then(function(value)  {
    // success
}, function(err){
    // failure
})

then ⽅法可以接受两个回调函数作为参数。成功回调和失败回调,其中,第⼆个函数是可选的,不⼀定要提供。这两个函数都接受Promise对象传出的值作为参数。

需要注意的是:

  • Promise新建后会立即执行,
  • then方法指定的回调函数将在一步操作后执行

手写Promise:

从Promise的使用来看可以分为两部分:新建实例和调用实例的then方法。 所以我们也可以针对这两部分分别实现:

  • 实现Promise构造函数
  • 实现then方法
实现Promise构造函数

首先从构造函数出发,根据上文对Promise的描述,我们来实现这个Promise构造函数。

Promise构造函数接受⼀个函数作为参数,该函数的两个参数分别是resolve和reject。他们的作用是将state从Pending变为fulfilled或者rejected

function Promise (executor) {
    this.state = 'pending';
    this.data = undefined;
    this.reason= undefined;
    this.fn1Callbacks=[];
    this.fn2Callbacks=[];
    let resolve = value => {
        if(this.state === 'pending'){
            this.state = 'fulfilled';
            this.data = value;
            for(let i = 0; i<this.fn1Callbacks.length; i++){
                self.fn1Callbacks[i](value);
            }
        }
    };
    let reject = reason => {
        if(this.state === 'pending'){
            this.state = 'rejected';
            this.reason = reason;
            for (let i = 0; i < self.fn2Callback.length; i++) {
                self.fn2Callback[i](reason);
            }
        }
    };
    try {
        executor(resolve, reject);
    } catch (err) {
        reject(err);
    }
}
实现 then 方法

当Promise的状态发生了改变,不论是成功或是失败都会调用then方法。

Promise实例具有then方法,即then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数并在异步完成时执行这个函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

基于上面的讨论我们很容易得出结论:

  • then方法可以在实例上调用。因此then 方法的实现是在Promise的 prototype上。
  • 他的作用是为Promise实例添加状态改变时的回调函数并在异步完成时执行这个函数
  • then方法会返回一个Promise,而且是返回一个新的Promise(详情)对象。

下面我们来看看promise实现

// then方法接收两个参数,fn1,fn2,分别为Promise成功或失败后的回调
Promise.prototype.then = function(fn1, fn2) {
  var self = this
  var promise2

  // 首先对入参 fn1, fn2做判断
  fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
  fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
        //todo
    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
       //todo
    })
  }

  if (self.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
       // todo
    })
  }
}

首先是对输入的成功回调和失败回调进行类型判断,明确是个函数再执行它,接下来是处理Promise中可能存在的有三种可能的状态,我们分三个if块来处理,在里面分别都返回一个new Promise。

所以,接下来的逻辑是:

  • 如果 promise 状态是 resolved,需要执行 fn1 ;
  • 如果 promise 状态是 rejected, 需要执行fn2 ;
  • 如果 promise 状态是 pending, 我们并不能确定调用 fn1 还是 fn2 ,只能先把方法都保存在 fn1Callback, fn2Callback 数组中。等到Promise的状态确定后再处理。

根据上面的逻辑,填充下面代码:

Promise.prototype.then = function(fn1, fn2) {
    var self = this
    var promise2
    fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
    fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
    if (self.status === 'resolved') {
        return promise2 = new Promise(function(resolve, reject) {
            // 把 fn1、fn2 放在 try catch 里面,毕竟 fn1、fn2 是用户传入的,报错嘛,很常见
            try {
                var x = fn1(self.data)
                // fn1 执行后,会有返回值,通过 resolve 注入到 then 返回的 promise 中
                resolve(x)
            } catch (e) {
                reject(e)                
            }
        })
    }
    if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
            try {
                var x = fn2(self.data)
                reject(x)
            } catch (e) {
                reject(e)
            }
        })
    }
    if (self.status === 'pending') {
        return promise2 = new Promise(function(resolve, reject) {
            this.fn1Callback.push(function(value){
                try {
                    var x = fn1(self.data);
                    resolve(x)
                } catch (e) {
                    reject(e)
                }
            })
            this.fn2Callback.push(function(value) {
                try {
                    var x = fn2(self.data);
                    reject(x)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}