开篇我们仍然解决一下上篇提出的问题:并行执行,顺序输出
熟悉一个事物的最佳方式就是自己创造一遍,亲身体会一下创作过程中遇到的问题和你的解决方案与标准之间的差异。
Promise 实现(demo)思路
1、构造函数形式
2、调用流程
3、then方法链式调用
4、不同异步形式的组合, 如catch, done, finally,race,all等方法
一、构造函数形式
var promise = new Promise(function (resolve, reject) {if (/* 异步操作成功 */){resolve(value);} else { /* 异步操作失败 */reject(new Error());}
});
1、 接收函数作为参数
2、 该函数形参resolve、reject分别对应异步操作成功、失败时调用的函数
更好的诠释是,当当前程序执行完成时,由promise充当的中介,通知未来的程序可以执行了,这里的未来定义在then方法中
3、 resolve、reject承担两个责任:
1) 异步操作成功或失败时,改变promise状态、
2) 透传value或error值
现在我们实现其构造函数:
//定义状态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise(){constructor(handle){if(typeof handle !== 'function'){throw new Error('not function type')}this._state = PENDINGthis._value = nulltry{//handle函数会立即执行handle(resolve, reject)}catch(err){//...}}
}
那么,现在有两个问题:
1、 resolve、reject这两个函数定义在哪里?
2、promise的调用流程是什么?then方法中传入的回调在什么时候回被执行?
调用流程
从一个封装Ajax的例子讲起:
let {method, url, isAsync} = optionsvar p = new Promise(function (resolve, reject) {xhr.open(method, url, true);xhr.onload = function (e) {if (this.status === 200) {result = JSON.parse(this.responseText);//当请求资源成功时,调用resolve方法//更改PENDING为FULFILLED, 且该变更不可逆//同时接受参数resultresolve(result);}};xhr.onerror = function (e) {//当请求资源失败时,调用reject方法//更改PENDING为REJECTED, 且该变更不可逆//同时接受参数ereject(e);};xhr.send();});p.then(console.log, console.err)//then方法中传入的回调,在状态变更后,被执行
由上例推演出:
1、resolve、reject函数定义在该Promise类中(非用户自定义),完成状态变更,结果保存的任务。
2、调用then方法时,会延时执行回调,直到状态变更。那么这些回调一定会被保存,当状态变更时触发。
给出resolve、reject、then方法的定义:
//定义状态常量const PENDING = 'PENDING'const FULFILLED = 'FULFILLED'const REJECTED = 'REJECTED'class MyPromise{constructor(handle){ //省略...try{handle(this._resolve.bind(this), this._reject.bind(this))}catch(err){//...}}_resolve(value){if(this._state !== PENDING){return}//变更状态this._state = FULFILLED//记录value值this._value = value//触发对应回调this.onFulfilled(value)}_reject(err){if(this._state !== PENDING){return}this._state = REJECTEDthis._value = errthis.onFulfilled(err)}then(onFulfilled, onRejected){const {_state} = thisswitch(_state){case PENDING:this.onFulfilled = onFulfilledthis.onRejected = onRejectedbreak}}
}
这里有两个问题:
1、如果then方法中传入的不是函数怎么办?
2、此时的then方法只能调用一次,而标准的then方法可以一直链式调用,如何实现?
链式调用
这里我们直接给出解决方法:then方法存在于Promise类中,只要我们在then方法中返回一个promise实例,即可实现链式调用。
那么,此时then方法中的回调函数就有一系列要求:
1、对于then方法传入的不是函数,可直接忽略该函数,将结果透传给下一个promise。参考标准2.2.7.3, 2.2.7.4
2、如果传入then方法的回调函数执行之后,返回值为promise对象,那么需要等到该对象状态变更之后再调用后续then方法中的回调。参考**标准2.2.6 **
3、…
根据标准,我们可以这么组织代码:
//...
function isFunction(fun){return Object.prototype.toString.call(fun)
}class MyPromise{constructor(handle){ if(!isFunction(handle)){throw new Error('not function type')}//...this.fulfilledQueue = []this.rejectedQueue = []//...}//执行队列中成功回调runFulfilled (value) {let cbwhile(cb = this.fulfilledQueue.shift()){cb(value)}}//执行队列中失败回调runRejected (value) {while(cb = this.fulfilledQueue.shift()){cb(value)}}_resolve(value){if(this._state !== PENDING){return}//返回值为promise时,需要等待该promise状态变更后才能执行对应回调if(value instanceof MyPromise){value.then(res => {this._state = FULFILLEDthis._value = valuethis.runFulfilled(res)}, err => {this._state = REJECTEDthis._value = valuethis.runRejected(err)})}else{this._state = FULFILLEDthis._value = valuethis.runFulfilled(value)}}_reject(err){if(this._state !== PENDING){return}this._state = REJECTEDthis._value = errrunRejected(err)}then(onFulfilled, onRejected){const {_state, _value} = thisreturn new MyPromise((onFulfilledNext, onRejectedNext)=>{//对于成功回调函数的封装const fulfilled = val =>{try{//若onFulfilled不是函数,则直接透传value值if(!isFunction(onFulfilled)){onFulfilledNext(val)}else{//判断onFulfilled执行之后,返回值res是不是promise实例,如果是则需要等待其状态变更后再执行后续回调const res = onFulfilled(val)if(res instanceof MyPromise){res.then(onFulfilledNext, onRejectedNext)}else{onFulfilledNext(val) }}}catch(err){return onRejectedNext(err)}}//对于失败回调函数的封装const rejected = error => {try{if(!isFunction(onRejected)){onRejectedNext(error)}else{const res = onRejected(error)if(res instanceof MyPromise){res.then(onFulfilledNext, onRejectedNext)}else{onRejectedNext(error) }}}catch(err){return onRejectedNext(err)}}switch(_state){case PENDING:this.fulfilledQueue.push(fulfilled)this.rejectedQueue.push(rejected)breakcase FULFILLED:this.fulfilled(_value)breakcase REJECTED:this.rejected(_value)} })}
}
//一个小测验
const read = require('fs').readFile
const handler = function(resolve, reject){read('./index.txt', (err, data)=>{resolve(data.toString())})
}
const promise = new MyPromise(handler)
promise.then(res => console.log(res), console.err)
promise可组合函数
写到上面,一个基本的primise demo库已经够看了,其他的还有一些内置方法,我们可以写一写,以提高对这些方法的认识。
promise的方法分为部分可被实例继承如then、catch方法等,另一部分只能由Promise类提供(static)描述。
1、_resolve|_reject
class MyPromis{//..._resolve(value){if(value instanceof MyPromise){return value}return new Promise((resolve)=>{resolve(value)})}_reject(value){if(value instanceof MyPromise){return value}return new Promise((resolve,reject)=>{reject(value)})}
}
2、 all | race
class MyPromis{
//...//语义参考其他教程static all(list){return new Promise((resolve, reject)=>{let count = 0let length = list.lengthconst values = []for(let promise of list){this._resolve(promise).then(res => {conut ++values.push(res)if(count ===length){resolve(values)}}, err => { reject(err) })}})}static race(list){return new Promise((resolve, reject)=>{for(let promise of list){this.resolve(promise).then(res => resolve(res))}})}//...
}
3、 done | catch | finally 接收一组promise实例,返回一个promise对象,当所有promise实例状态变更为FULFILLED时,返回值状态才变更为FULFILLED,否则为REJECTED。
class MyPromis{//...//catch方法用于reject时, 执行对应回调catch (onRejected) {return this.then(undefined, onRejected)}//done方法不在promise的规范当中,是then方法的语法糖done(onFulfilled, onRejected){this.then(onFulfilled, onRejected).catch(err => {setTimeout(()=>{throw err},0)})}//finally方法接收函数为参数,且无论前一个then方法状态是什么,该回调必定执行finally(cb){this.then(value => MyPromise.resolve(cb()).then(() => value)reason => MyPromise.resolve(cb()).then(()=> { throw reason}))}}
一个非常重要的尾巴
看下边一段代码:
console.log('start')let promise = new MyPromise((resolve, reject) => {console.log('in promise')resolve('hahahha')})console.log(promise)promise.then( console.log ) // 无输出console.log('end')// 输出顺序为:start -> in promise -> hahahha -> end ->MyPromise {_state: 'FULFILLED',_value: null,fulfilledQueue: [ [Function: fulfilled] ],rejectedQueue: [ [Function: rejected] ] }hahaha
该结果为Event Loop(事件循环)中定义promise为微任务的规定不符。
分析:
class MyPromise{constructor(handle){//...handle(this.resolve.bind(this), this.reject.bind(this))}resolve(value){//...}
}
handle在实例化MyPromise时,会立即执行。导致resolve函数立即执行!
因此,在打印promise实例时,其状态即为**‘FULFILLED’**
做如下修改:
class MyPromise{constructor(handle){//...handle(this.resolve.bind(this), this.reject.bind(this))}resolve(value){const run = () => {//原来的代码...}setTimeout(run, 0)}reject(value){const run = () => {//原来的代码...}setTimeout(run, 0)}
}
此时,我们在看看看下上面代码的输出值。
// 输出顺序为:start -> in promise -> end -> hahahha ->
MyPromise {_state: 'PENDING',_value: null,fulfilledQueue: [ [Function: fulfilled] ],rejectedQueue: [ [Function: rejected] ] }
hahaha
与标准一致! 打完收工~