人类补完计划ES6总结从零开始实现一个符合A+规范的Promise
Tom从零开始实现一个符合A+规范的Promise
Promise是什么,解决了什么问题?
Promise是一种用于异步编程的JavaScript对象。主要用于处理异步操作 的结果。
异步导致的问题:回调地狱(让代码难以阅读)、错误处理(无法统一处理错误)、多个异步操作(同步结果困难)
- Promise可以使用.then() 方法链式处理异步逻辑
- Promise可以使用catch().方法处理异步操作失败的情况
- Promise提供.all()、.race()方法支持处理多个Promise对象的结果
开始实现一个符合A+规范的Promise
最基础的版本
我们平常使用Promise时的用法通常是这样的
1 2 3 4 5 6 7 8 9 10 11 12
| const p1 = new Promise((resolve,reject)=> { resolve('成功') })
p1.then(value => { console.log('成功',value) },error => { console.log('失败',reason) })
|
从这种使用方法结合平常的使用我们可以知道Promise具有以下特点:
- Promise是一个类,使用的时候需要new Promise来产生一个promise实例
- 构造函数中需要传递一个参数 executor , executor是立即执行的
- executor参数中有两个参数 resolve(value) reject(reason)
- 调用resolve会让promise 变为成功,reject会变成失败
- 有三种状态(pending等待态、fulfilled成功态、rejected失败态),一旦状态发生改变后不能再修改状态
- 每个promise实例都有一个then方法,会有两个参数 onfulfilled onrejected
根据这些特性,我们可以写出基础版本的promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED'
class Promise { constructor(executor){ this.status = PENDING
this.value = undefined this.reason = undefined const resolve = (value) => { if(this.status === PENDING){ this.value = value this.status = FULFILLED } } const reject = (reason) => { if(this.status === PENDING){ this.reason = reason this.status = REJECTED } } try{ executor(const resolve,const reject) }catch(err){ const reject(err) } } then(onFulfilled,onRejected){ if(this.status === FULFILLED){ onFulfilled(this.value) } if(this.status === REJECTED){ onRejected(this.reason) } } }
module.exports = Promise
|
增加异步处理
上面的版本如果立即修改promise 状态的话能成功,但是promise是用来解决异步问题的,这样就会存在问题
1 2 3 4 5 6 7 8 9 10 11 12
| const p1 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve('success') },2000) })
p1.then((value)=>{ console.log('success',value) },(reason)=>{ console.log('error',reason) })
|
所以我们需要在原型上增加 onFulfilledCbs,onRejectedCbs 两个数组, 当处于pending状态时分别把then中成功的方法和失败的方法放入数组中,当状态改变时循环执行数组中方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED' class Promise { constructor(executor){ this.status = PENDING this.value = undefined this.reason = undefined this.onFulfilledCbs = [] this.onRejectedCbs = [] const resolve = (value) => { if(this.status === PENDING){ this.value = value this.status = FULFILLED this.onFulfilledCbs.forEach(fn=> fn()) } } const reject = (reason) => { if(this.status === PENDING){ this.reason = reason this.status = REJECTED this.onRejectedCbs.forEach(fn=> fn()) } } try{ executor(const resolve,const reject) }catch(err){ const reject(err) } } then(onFulfilled,onRejected){ if(this.status === FULFILLED){ onFulfilled(this.value) } if(this.status === REJECTED){ onRejected(this.reason) } if(this.status === PENDING){ this.onFulfilledCbs.push(()=>{ onFulfilled(this.value) }) this.onRejectedCbs.push(()=>{ onRejected(this.reason) }) } } }
module.exports = Promise
|
链式调用
上面的代码.then链式调用的时候就会存在问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const fs = require('fs') const path = require('path')
function readFile(url){ return new Promise((resolve,reject)=>{ fs.readFile(url,'utf8',function(err,data){ if(err) return reject(err) resolve(data) }) }) }
let promise2 = readFile(path.resolve(__dirname, 'name.txt')) .then((data) => { return 100 }) promise2.then(data => { console.log('then2 success',data) }, err => { console.log('then2 error',err) })
|
promise的核心是能进行链式调用,链式调用分为以下几种情况
- 返回的是一个普通值(非promise得值)这个值会被传到外层then的下一个then的成功中
- 没有返回值 (抛错了),会执行外层then的下一个then的失败
- 返回的是promise,会去解析返回的promise将解析后的值,传递到成功或者失败中(看这个promise是什么)
链式调用一般情况需要返回的是this,promise为了能扭转状态 而且还要保证promise状态变化后不可更改,返回一个全新的promise(promise2)
then 方法可以优化为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| then(onFulfilled, onRejected) { let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { try { let x = onFulfilled(this.value) resolve(x) } catch (e) { reject(e) } } if (this.status === REJECTED) { try { let x = onRejected(this.reason) resolve(x) } catch (e) { reject(e) } } if (this.status === PENDING) { this.onFulfilledCbs.push(() => { try { let x = onFulfilled(this.value) resolve(x) } catch (e) { reject(e) } }) this.onRejectedCbs.push(() => { try { let x = onRejected(this.reason) resolve(x) } catch (e) { reject(e) } }) } }) return promise2 }
|
resolvePromise
上面的链式调用返回的是一个普通值的话已经可以正常链式调用传递给下一个then了,但是如果返回的也是一个promise就需要进一步处理
1 2 3 4 5 6 7 8 9 10 11 12
| let promise2 = readFile(path.resolve(__dirname, 'name.txt')) .then((data) => { return (new Promise(resolve,reject)=>{}) }) promise2.then(data => { console.log('then2 success',data) }, err => { console.log('then2 error',err) })
|
就需要我们去解析返回值(A+规范中有具体的要求)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| function resolvePromise(x,promise2,resolve,reject){ if (x === promise2){ return reject(new TypeError()) }
if((typeof x === 'object' && x!== null) || (typeof x === 'function')){ let called = false try{ let then = x.then if(typeof then === 'function'){ then.call(x,(y)=>{ if(called) return called = true resolvePromise(y,promise2,resolve,reject) },(r)=>{ if(called) return called = true reject(r) }) }else{ resolve(x) } }catch(e){ if(called) return called = true reject(e) } }else{ resolve(x) } }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function'? onFulfilled: data => data onRejected = typeof onRejected === 'function'? onRejected: reason => {throw reason} let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { process.nextTick(()=>{ try { let x = onFulfilled(this.value) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) } if (this.status === REJECTED) { process.nextTick(()=>{ try { let x = onRejected(this.reason) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) } if (this.status === PENDING) { this.onFulfilledCbs.push(() => { process.nextTick(()=>{ try { let x = onFulfilled(this.value) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) }) this.onRejectedCbs.push(() => { process.nextTick(()=>{ try { let x = onRejected(this.reason) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) }) } }) return promise2 }
|
完整版的promise实现
通过以上步骤就实现了完整版的promise
A+规范中是没有 Promise.all(),Promise.race等(),Promise.resolve()等等方法的要求的,现在已经是一个完整的Promise了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| function resolvePromise(x,promise2,resolve,reject){ if (x === promise2){ return reject(new TypeError()) } if((typeof x === 'object' && x!== null) || (typeof x === 'function')){ let called = false try{ let then = x.then if(typeof then === 'function'){ then.call(x,(y)=>{ if(called) return called = true resolvePromise(y,promise2,resolve,reject) },(r)=>{ if(called) return called = true reject(r) }) }else{ resolve(x) } }catch(e){ if(called) return called = true reject(e) } }else{ resolve(x) }
} const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED' class Promise { constructor(executor) { this.status = PENDING this.value = undefined this.reason = undefined this.onFulfilledCbs = [] this.onRejectedCbs = [] const resolve = (value) => { if (this.status === PENDING) { this.value = value this.status = FULFILLED this.onFulfilledCbs.forEach(fn => fn()) }
} const reject = (reason) => { if (this.status === PENDING) { this.reason = reason this.status = REJECTED this.onRejectedCbs.forEach(fn => fn()) } } try { executor(const resolve, const reject) } catch (err) { const reject(err) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function'? onFulfilled: data => data onRejected = typeof onRejected === 'function'? onRejected: reason => {throw reason} let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { process.nextTick(()=>{ try { let x = onFulfilled(this.value) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) } if (this.status === REJECTED) { process.nextTick(()=>{ try { let x = onRejected(this.reason) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) } if (this.status === PENDING) { this.onFulfilledCbs.push(() => { process.nextTick(()=>{ try { let x = onFulfilled(this.value) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) }) this.onRejectedCbs.push(() => { process.nextTick(()=>{ try { let x = onRejected(this.reason) resolvePromise(x,promise2,resolve,reject) } catch (e) { reject(e) } }) }) } }) return promise2 } }
|
如何确定自己实现的promise是否符合 A+ 规范呢
下载检测工具 yarn global add promise-aplus-tests
在尾部添加如下代码
1 2 3 4 5 6 7 8 9 10
| Promise.deferred = function(){ const dfd = {} dfd.promise = new Promise((resolve,reject)=>{ dfd.resolve = resolve dfd.reject = reject }) return dfd }
|
- 运行检测
promises-aplus-tests myPromise.js

哈哈,测试全部通过
其他常用方法的实现
Promise.resolve
- Promise.resolve 有一个特点就是会产生一个新的promise
1 2 3 4 5
| Promise.resolve = function(value){ return new Promise((resolve,reject)=>{ resolve(value) }) }
|
- Promise.resolve 可以即系传入的promise 具备等待的效果
1 2 3 4 5 6 7 8 9 10 11 12 13
| const resolve = (value) => { if(value instanceof Promise){ return value.then(resolve,reject) } if (this.status === PENDING) { this.value = value this.status = FULFILLED this.onFulfilledCbs.forEach(fn => fn()) } }
|
Promise.reject
1 2 3 4 5
| Promise.reject = function(error){ return new Promise((resolve,reject)=>{ reject(error) }) }
|
Promise.catch
1 2 3
| catch(errFn){ return this.then(null,errFn) }
|
Promise.all
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Promise.all = function(promises){ return new Promise((resolve,reject)=>{ let idx = 0 const result = [] promises.forEach((item,i)=>{ Promise.resolve(item).then((data)=>{ result[i] = data if(++idx === promises.length){ resolve(result) } },()=> reject()) }) }) }
|
Promise.race
1 2 3 4 5 6 7
| Promise.race = function(promises){ return new Promise((resolve,reject)=>{ promises.forEach((item,i)=>{ Promise.resolve(item).then(resolve,reject) }) }) }
|
Promise.finally
1 2 3 4 5 6 7 8 9
| Promise.prototype.finally = function(fn){ return this.then((val)=>{ return Promise.resolve(fn()).then(() => val) },(r)=>{ return Promise.resolve(fn()).then(()=> {throw r}) }) }
|