手写Promise

307 阅读7分钟

一、简易版Promise

// 三种状态是固定的常量
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
    const that = this
    that.state = PENDING
    // value初始值为undefined
    that.value = undefined
    that.resolvedCallbacks = []
    that.rejectedCallbacks = []
    // 待完善resolve和reject函数
    // 待完善执行 fn 函数
}
  • 首先我们创建了三个常量用于表示状态,对于经常使用的一些值都应该通过常量来管理,便于开发及后期维护。
  • 在函数体内部首先创建了常量that,因为代码可能会异步执行,用于获取正确的this对象
  • 一开始Promise的状态应该是pending
  • value 变量用于保存 resolve 或者 reject 中传入的值
  • resolvedCallbacksrejectedCallbacks 用于保存 then 中的回调,因为当执行完 Promise 时状态可能还是等待中,这时候应该把then中的回调保存起来用于状态改变时使用

接下来我们来完善 resolvereject函数,添加在 MyPromise函数体内部

function resolve(value) {
    if (that.state === PENDING) {
        that.state = RESOLVED
        // 触发resolve方法时,value会被重新赋值
        that.value = value
        that.resolvedCallbacks.map(cb => cb(that.value))
    }
}

function reject(value) {
    if (that.state === PENDING) {
        that.state = REJECTED
        that.value = value
        that.rejectedCallbacks.map(cb => cb(that.value))
    }
}
  • 首先两个函数都得判断当前状态是否都为等待中,因为规范只有等待态才可以改变状态
  • 将当前状态更改为对应状态,并且将闯入的值赋值给value
  • 遍历回调数组并执行

完成以上两个函数以后,我们就该实现如何执行Promise中传入的函数了

try {
    fn(resolve, reject)
} catch (e) {
    reject(e)
}
  • 实现很简单,执行传入的参数并且将之前两个函数当做参数传进去
  • 要注意的是,可能执行函数过程中会遇到错误,需要捕获错误并且执行reject函数

最后我们来实现较为复杂的then函数

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const that = this
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected == 'function' ? onRejected : r => {
        throw r
    }
    if (that.state === PENDING) {
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallbacks.push(onRejected)
    }
    if (that.state === RESOLVED) {
        onFulfilled(that.value)
    }
    if (that.state === REJECTED) {
        onRejected(that.value)
    }
}
  • 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  • 当参数不是函数类型时,需要创建一个函数赋值为对应的参数,同时也实现了透传,如下代码
// 该代码目前在简单版中会报错,只是作为一个透传的例子
Promise.resolve(4).then().then((value) => console.log(value))
  • 接下来就是一系列判断状态的逻辑,当状态不是等待时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数,比如如下代码就会进入等待态的逻辑
new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    },0)
}).then(value => {
    console.log(value)
})

以上就是简单版Promise的实现,最终代码如下:

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
    const that = this
    that.state = PENDING
    that.value = null
    that.resolvedCallbacks = []
    that.resolvedCallbacks = []
    
    function resolve(value) {
        if (that.state === PENDING) {
            that.state = RESOLVED
            that.value = value
            that.resolvedCallbacks.map(cb => cb(that.value))
        }
    }
    
    function reject(value) {
        if (that.state === PENDING) {
            that.state = REJECTED
            that.value = value
            that.rejectedCallbacks.map(cb => cb(that.value))
        }
    }
    
    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const that = this
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => {
        throw r
    }
    
    if (that.state === PENDING) {
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallbacks.push(onRejected)
    }
    
    if (that.state === RESOLVED) {
        onFulfilled(that.value)
    }
    if (that.state === REJECTED) {
        onRejected(that.value)
    }
}
// 此匿名函数接受两个参数resolve,reject
new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    },0)
}).then(value => {
  console.log(value)
})

Promise A+ 唯有多写

// then执行比Promise晚
// reject 拒绝当前服务
// 完善Promise状态 pending ---> fulfilled
// promise.all 所有的promise进行调用 原子操作
// race 只要有一个状态发生变化值就返回
function Promise (fn) {
    var callback;
    this.then = function (done) {
        // callback赋值为回调函数function
        callback = done
    }
    // 先执行resolve
    function resolve (data) {
        setTimeout(function () {
            // 先执行了then,geicallback赋值
            callback(data)
        },0)
    }
    fn(resolve)
}

使用

new Promise(function (resolve, reject) {
    resolve("解决")
}).then(function (data) {
    console.log(data)
})
new Promise(function (resolve, reject) {
    resolve("解决")
    // 不会执行
    setTimeout( () => {
        resolve("解决1")
    }, 2000)
}).then(function (data) {
    console.log(data)
})
// 解决
console.log(1)
new Promise(resolve => {
    console.log(2)
    resolve()
    console.log(3)
}).then(() => {
    console.log(4)
})
console.log(5)

// 1 2 3 4 5

注意,调用resolvereject并不会终结Promise的参数函数的执行。 上面代码中,调用resolve()后,console.log(3)还是会执行。 因此,最好在resolvereject前面加上return,这样就不会继续执行了。

Promise.prototype.finally() finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。 Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例。

二、Promise.resolve()

Promise.resolve(value)方法返回一个以给定值value解析后的Promise对象。如果该值为promise,返回这个promise;如果这个值是thenable(即带有"then"方法),返回的promise会"跟随"这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

Promise.resolve('foo')
  .then(Promise.resolve('bar'))
  .then(function (result) {
      console.log(result)
  })
// foo

var promise1 = Promise.resolve(123);
promise1.then(function(value) {
  console.log(value); // 123
});

// 使用静态Promise.resolve方法
Promise.resolve("Success").then(function(value) {
  console.log(value); // "Success" 此时的状态机为resolve
}, function(value) {
  // 不会被调用
});

// resolve一个数组
var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});
  • Resolve另一个promise

此方法和Promise.resolve()嵌套 async await 一个道理

var original = Promise.resolve(33)
var cast = Promise.resolve(original)
cast.then(value => {
  console.log('value' + value)
})
console.log('original === cast' + (original === cast))
/*
*  打印顺序如下,这里有一个同步异步先后执行的区别
*  original === cast ? true
*  value: 33
*/

从上面的打印结果说明promise嵌套promise之后的值和promise是等价的,因此在KOA2中即使外层嵌套Promise.resolve()但并不影响结果。Promise.resolve()方法是返回resolve的状态机。

// Resolve一个thenable对象
var p1 = Promise.resolve({
    then: function(onFulfill, onReject) { onFulfill("fulfilled!")}
})
console.log(p1 instanceof Promise) // true, 这是一个Promise对象

p1.then(function(v) {
    console.log(v); // 输出"fulfilled!"
}, function(e) {
    // 不会被调用
})

// Thenable在callback之前抛出异常
// Promise rejects
var thenable = { then: function(resolve) {
    throw new TypeError('Throwing')
    resolve("Resolving")
}}

var p2 = Promise.resolve(thenable);
p2.then(function(v) {
  // 不会被调用
}, function(e) {
  console.log(e); // TypeError: Throwing
})

// Thenable在callback之后抛出异常
// Promise resolves
var thenable = { then: function(resolve) {
    resolve("Resolving");
    throw new TypeError("Throwing");
}}

var p3 = Promise.resolve(thenable);
p3.then(function(v) {
  console.log(v); // 输出"Resolving"
}, function(e) {
  // 不会被调用
});

三、Promise.all()

Promise.all(iterable)方法返回一个Promise实例,此实例在iterable参数内所有的promise都"完成(resolved)"或参数中不包含promise时回调完成(resolve);如果参数中promise有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败promise的结果。

var promise1 = Promise.resolve(3)
var promise2 = 42
var promise3 = new Promise((resolve, reject) {
   // setTimeout第三个以后的参数是作为第一个func()的参数传进去。
    setTimeout(resolve, 100, 'foo')
}) 

Promise.all([promise1, promise2, promise3]).then((value) {
    console.log(values) // [3, 42, "foo"]
})
  • 返回值
  • 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的Promise
  • 如果传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved) Promise。注意:Google Chrome 58 在这种情况下返回一个已完成(already resolved)状态的 Promise
  • 其它情况下返回一个处理中(pending)Promise。这个返回的 promise 之后会在所有的promise 都完成或有一个promise 失败时异步地变为完成或失败。 见下方关于“Promise.all 的异步或同步”示例。返回值将会按照参数内的promise 顺序排列,而不是由调用 promise的完成顺序决定。
// setTimeout
var doc=document.getElementById('div');
setTimeout(function(){
    doc.style.color='red';
},10000,setTimeout(function(){
    doc.style.color='black';
},5000));

上面的结果是,div元素内的字体样式5秒后变黑,10秒后再变红。是不是很惊奇,因为第三个参数也是一个定时器,5后就会开启。和JQuery里面的animate()不同,animate里面回调是执行了前面之后再执行后面的。

说明

  • 此方法在集合多个 promise 的返回结果时很有用。

  • 完成(Fulfillment: 如果传入的可迭代对象为空,Promise.all 会同步地返回一个已完成(resolved)状态的promise。 如果所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promisePromise.all 返回的 promise 异步地变为完成。 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。

  • 失败/拒绝(Rejection: 如果传入的promise中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。

  • 实例

  • Promise.all 的使用 如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中(如果 promise 完成的话):

var p = Promise.all([1,2,3]) // Promise { <state>: "fulfilled", <value>: Array[3] }
p.then(v => console.log(v)) // [1, 2, 3]

var p2 = Promise.all([1,2,3, Promise.resolve(444)]) // Promise { <state>: "fulfilled", <value>: Array[4] }

var p3 = Promise.all([1,2,3, Promise.reject(555)]) // Promise { <state>: "rejected", <reason>: 555 }

参考文档:

  1. promisesaplus.com/
  2. www.ituring.com.cn/article/665…