JavaScript进阶之手写Promise

前言

Promise是当前ES6语法中的异步解决方案,本文从基本概念开始,层层分析,一步一步手写实现Promise,但愿能和你们一块儿完全掌握Promise。 javascript

概述

Promise是异步编程的一种解决方案,跟传统回调函数来实现异步相比,其设计更合理,代码更易懂。Promise是"美颜后"的异步解决方案.
在ES6中, 其写进了语言标准提供原生支持。
Promise有两大特色:html

  1. 对象的状态不受外界的影响,Promise对象表明一个异步操做,有三种状态: pending , fulfilled , rejected
  2. Promise是一个状态机,一旦状态改变,就不会再变,而且在任什么时候候均可以获得这个结果。

上图摘自MDNjava

Promise的具体用法能够参考阮一峰老师的 《ECMAScript 6 入门》MDN
这里有几个点须要注意:git

  1. Promise能够有finally,用于作跟状态无关的业务逻辑操做。
  2. Promise中定义的函数在resolve以后仍是能够执行语句,因此建议在resolve前加上return
new Promise((resolve, reject) => {
  resolve(1);//为了防止输出2,改为return resolve,平常编码中也要养成return resolve的习惯
  console.log(2);// 能够输出2
}).then(r => {
  console.log(r);
});
复制代码
  1. then方法中的第二个参数 reject=>{} 能够省略,建议使用 catch(error=>{}) ,其不只可以获取reject的内容,还能够捕获到Promise函数和then函数中产生的异常。Promise内部的异常会被吃掉,不会被外部的异常捕获。
  2. 注意Promise.all和Promise.race的用法及使用场景。

若是熟悉了Promise的使用,其实咱们知道,Promise提供了异步编程的语法糖,使原来异步回调的操做能够用同步的方式来表达。es6

回调地狱

在传统AJAX异步解决方案中,咱们通常使用回调函数来解决数据的接收和处理,以下:github

$.get(url, (data) => {
    console.log(data)
)
复制代码

在某些需求场景下,咱们须要发送多个异步请求,而且每一个请求之间结果之间须要相互依赖,随着回调函数相互嵌套的增长,函数之间的逻辑就会耦合在一块儿,难以维护,造成回调地狱。以下所示:面试

let country = 'china';
let city = 'shanghai';
let district = 'PD'
$.get(`xxxxx/countries`,countries=>{
  /** **这里能够再第一个select控件中,渲染国家列表, **/
  countries.forEach((item)=>{
  		if(item===country){
        //查找并选中当前国家
        $.get(`xxxxx/findCitiesByCountry/${country}`, cities => {
             /** **这里能够再第二个select控件中,渲染城市列表, **/
            cities.forEach((item)=>{
              if(item === city){
               //查找并选中当前城市
               $.get(`xxxxx/findDistrictsByCity/${city}`, dists => {
											  /** **这里能够再第三个select控件中,渲染地区列表, **/
                 			dists.forEach(item=>{
                      	if(item==district){
                        	 //查找并选中地区
                        }
                      })
               })
              }
            })
        })
      }
  });
});
复制代码

上述是一个简单的三级联动功能,使用三个回调函数。它们相互嵌套逻辑复杂,耦合严重。
Promise解决了回调地狱问题,经过Promise将上述代码改写成编程

let country = 'china';
let city = 'shanghai';
let district = 'PD'
new Promise(() => {
    $.get(`xxxxx/countries`, countries => {
        return countries;
    });
}).then((countries) => {
    countries.forEach((item) => {
        if (item === country) {
            $.get(`xxxxx/findCitiesByCountry/${country}`, cities => {
                return cities;
            })
        }
    })
}).then((cities) => {
    cities.forEach((item) => {
        if (item === city) {
            $.get(`xxxxx/findDistrictsByCity/${city}`, dists => {
                return dists;
            })
        }
    })
}).then((dists) => {
    dists.forEach(item => {
        if (item == district) {
            //查找并选中地区
        }
    })
})
复制代码

此时,将异步执行由原来的回调,改为了 then...then....then... 链式调用的方式。
线性的链式执行(同步)更符合人类的思考习惯(更直白的说,按照顺序一步一步的闭环符合人类思考习惯。Promise就是将本来异步回调的语法形式,改写成同步。因此实现Promise,就是把异步回调这种丑陋的方式改为链式调用。经过手写Promise,咱们来理解和消化其设计思想。 数组

开始

有了上述的铺垫,咱们了解Promise的概念和特征,也知道了Promise的优点,下面咱们一步步来实现Promise。promise

  1. Promise是一个构造函数,而且传入的参数是一个函数,而且该函数在构造函数中执行
function Promise(executor){
	try{
    executor()
  }catch(e){
  	console.log(e):
  }
}
复制代码
  1. executor函数的两个参数resolvereject,是executor函数中的回调函数。
function Promise(executor){
  function resolve(value){
    //能够将executor中的数据传入resolve中
  }
  function reject(value){
    //能够将executor中的数据传入reject中
  }
	try{
    executor(resolve,reject)
  }catch(e){
  	console.log(e):
  }
}
复制代码
  1. Promise实现了状态机,在执行resolve时,由 PENDING=>FULFILLED ,在执行reject时,由 PENDING=>REJECTED 。
const pending = 'PENDING';
const rejecting = 'REJECTED';
const fulfilled = 'FULFILLED';
function Promise(executor){
  var that = this;
  that.status = pending;
  that.value = null;
  that.error = null;
  function resolve(val){
    //当且仅当PENDING=》FULFILLED
    if(that.status === pending){
      that.status = fulfilled;
    	that.value = val;
    }
  }
  function reject(val){
    //当且仅当PENDING=》REJECTED
    if(that.status === pending){
      that.status = rejecting;
    	that.error = val;
    }
  }
  try{
    executor(resolve,reject);
  }catch(e){
    //在executor中产生的异常在reject中能够捕获。可是reject的异常,智能catch捕获
  	reject(e);
  }
}

Promise.prototype.then = function(onFulfilled, onRejected){
	var that = this;
  if(that.status === fulfilled){
    //当状态改变后,执行then中的回调
  	onFulfilled(that.value);
  }
  if(that.status === rejecting){
    //同上
    onRejected(that.error)
  }
};
复制代码

执行以下代码

new Promise((resolve)=>{
    resolve(1);
  }).then((res)=>{
    console.log(res);
  });
复制代码

打印结果以下

  1. Promise是异步的

若是executor函数存在异步,则须要等待resolve或者reject回调执行才会执行then中的函数体。

此处使用回调来解决异步:
第一步:定义两个Promise的成员:onResolvedCallBack和onRejectCallBack,存储then函数中的入参。
第二步:当执行then函数时,若是当前状态仍是PENDING(此时executor内部有异步操做)那么就将then中的参数传入onResolvedCallBack和onRejectCallBack中。若是此时状态是非PENDING,那么直接执行传入的函数便可。
第三步:Promise中的resolve函数执行时,触发onResolvedCallBack或者onRejectCallBack中的函数。

具体代码以下:

const pending = 'PENDING';
const rejecting = 'REJECTED';
const fulfilled = 'FULFILLED';
function Promise1(executor) {
  var that = this;
  that.status = pending;
  that.value = null;
  that.error = null;
  that.resolvedCallbacks = [];
  that.rejectedCallbacks = [];
  
  function resolve(val) {
    if (that.status === pending) {
      that.status = fulfilled;
      that.value = val;
      that.resolvedCallbacks.map(cb => cb(that.value));
    }
  }
  
  function reject(val) {
    if (that.status === pending) {
      that.status = rejecting;
      that.error = val;
      that.rejectedCallbacks.map(cb => cb(that.value));
    }
  }
  
  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}


Promise1.prototype.then = function (onFulfilled, onRejected) {
  var that = this;
  //为了保证兼容性,then的参数只能是函数,若是不是要防止then的穿透问题
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : r => {
          throw r
        }
  if (that.status === pending) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }
  
  if (that.status === fulfilled) {
    onFulfilled(that.value);
  }
  
  if (that.status === rejecting) {
    onRejected(that.error)
  }

};
复制代码

执行以下一段代码:

let p = new Promise((resolve) => {
   setTimeout(() => {
      resolve(1);
   }, 3000)
});

p.then((res) => { console.log(res); });

console.log(2);
复制代码

打印结果以下:

image.png

咱们再来执行以下代码

let p = new Promise((resolve) => {
  resolve(1);
});

p.then((res) => { console.log(res); });

console.log(2);
复制代码

此处咱们打印结果为

image.png

可是真正的Promise打印结果为:先2后1

  1. Promise的then属于microtasks

microtask和macrotask是Event Loop的知识点,关于Event Loop能够参考阮一峰老师的《JavaScript 运行机制详解:再谈Event Loop》
此处咱们使用setTimeout来模拟then的microtasks(注:)

function resolve(value) {
  setTimeout(() => {
    if (that.state === PENDING) {
      that.state = RESOLVED
      that.value = value
      that.resolvedCallbacks.map(cb => cb(that.value))
    }
  }, 0)
}
function reject(value) {
  setTimeout(() => {
    if (that.state === PENDING) {
      that.state = REJECTED
      that.value = value
      that.rejectedCallbacks.map(cb => cb(that.value))
    }
  }, 0)
}
复制代码
  1. resolve支持传入Promise对象

咱们执行以下代码:

let p = new Promise((resolve) => {
  var a = new Promise((resolve) => { resolve(1) });
  resolve(a);
});

p.then((res) => {
  console.log(res);
});
复制代码

此处resolve传入的是Promise对象,打印结果为:

image.png

因此在resolve函数中须要对value作一次判断

function resolve(value) {
  if (val instanceof Promise) {
    return val.then(resolve, reject);
  }
  setTimeout(() => {
    if (that.state === PENDING) {
      that.state = RESOLVED
      that.value = value
      that.resolvedCallbacks.map(cb => cb(that.value))
    }
  }, 0)
}
复制代码
  1. then能够链式调用

在Promise中,在then中执行return语句,返回的必定是Promise对象,这也是then可以链式调用的缘由。
首先咱们将then中的以下片断

if (that.status === pending) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }
复制代码

变形

if (that.status === pending) {
  that.resolvedCallbacks.push(()=>{onFulfilled(that.value)});
  that.rejectedCallbacks.push(()=>{onRejected(that.value)});
}
复制代码

它们之间只是写法的差别,效果相同。

由于咱们须要对then里传入的函数onFulfilled, onRejected返回的值进行判断,因此咱们须要对then继续改写

if (that.status === pending) {
  that.resolvedCallbacks.push(()=>{const x = onFulfilled(that.value)});
  that.rejectedCallbacks.push(()=>{const x = onRejected(that.value)});
}
复制代码

由于then返回的是Promise,因此继续完善

if (that.status === pending) {
  return new Promise(resolve,reject){
  	that.resolvedCallbacks.push(()=>{const x = onFulfilled(that.value)});
  	that.rejectedCallbacks.push(()=>{const x = onRejected(that.error)});
  }
}
复制代码

执行onFulfilled和onRejected时,使用try...catch...,因此继续完善

let promise2 = null;
if (that.status === pending) {
  return promise2 = new Promise((resolve,reject)=>{
    that.resolvedCallbacks.push(()=>{
      try{
        const x = onFulfilled(that.value);
      }catch(e){
        reject(e);
      }
    });
    
    that.rejectedCallbacks.push(()=>{
      try{
        const x = onRejected(that.error);
      }catch(e){
        reject(e);
      }
    });
  });
}
复制代码

上述x是onFulfilled(that.value)和onRejected(that.error)的返回值,为了保证then能够链式调用,也就是promise2的resolve可以resolve一个Promise对象,可是x返回的多是Promise对象,多是值,也多是函数,那么此处须要对x进行适配一下。此时引入resolvePromise函数,实现以下:

/** * 对resolve 进行改造加强 针对x不一样值状况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {  
        // 若是从onFulfilled中返回的x 就是promise2 就会致使循环引用报错
        return reject(new TypeError('循环引用'));
    }

    let called = false; // 避免屡次调用
    // 若是x是一个promise对象 (该判断和下面 判断是否是thenable对象重复 因此无关紧要)
    if (x instanceof Promise) { // 得到它的终值 继续resolve
        if (x.status === PENDING) { // 若是为等待态需等待直至 x 被执行或拒绝 并解析y值
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject);
            }, reason => {
                reject(reason);
            });
        } else { 
            // 若是 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 
            x.then(resolve, reject);
        }
        // 若是 x 为对象或者函数
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try { // 是不是thenable对象(具备then方法的对象/函数)
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if(called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, reason => {
                    if(called) return;
                    called = true;
                    reject(reason);
                })
            } else { // 说明是一个普通对象/函数
                resolve(x);
            }
        } catch(e) {
            if(called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

复制代码

此时that.status === pending的代码块也要继续修改

if (that.status === pending) {
        return promise = new Promise1((resolve, reject) => {
            that.resolvedCallbacks.push(() => {
                try {
                    const x = onFulfilled(that.value);
                    resolvePromise(promise,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                });
                
                that.rejectedCallbacks.push(() => {
                    try {
                        const x = onRejected(that.error);
                    } catch (e) {
                        reject(e);
                    }
                });
            })
    }	
复制代码

同理that.status===fulfilled和that.status===rejecting的时候代码以下:

if (that.status === FULFILLED) { // 成功态
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value);
                    resolvePromise(promise2, x, resolve, reject); 
                } catch(e) {
                    reject(e); 
                }
            });
        })
    }

    if (that.status === REJECTED) {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(that.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

复制代码
  1. Promise的all和race

Promise.all的用法以下

const p = Promise.all([p1, p2, p3]).then((resolve)=>{},(reject)=>{});
复制代码

Promise.all方法接受一个数组做为参数,只有当数组的全部Promise对象的状态所有fulfilled,才会执行后续的then方法。
根据all的用法和特色,咱们推测Promise.all返回一个Promise对象,在Promise对象中去等待Promise数组对象的函数执行完毕,数组中的每一个对象执行完毕都+1,当等于数组的长度时,resolve数组对象中全部resolve出来的值。

Promise.all = function(promises) {
    return new Promise((resolve, reject) => {
        let done = gen(promises.length, resolve);
        promises.forEach((promise, index) => {
            promise.then((value) => {
                done(index, value)
                //每执行一次都会往values数组中放入promise对象数组成员resolve的值
                //当values的长度等于promise对象数组的长度时,resolve这个数组values
            }, reject)
        })
    })
}

//使用闭包,count和values在函数的声明周期中都存在
function gen(length, resolve) {
    let count = 0;
    let values = [];
    return function(i, value) {
        values[i] = value;
        if (++count === length) {
            console.log(values);
            resolve(values);
        }
    }
}
复制代码

Promise.race的用法和all相似,区别就是promise对象数组其中有一个fulfilled,就执行then方法,实现以下

Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
           promise.then(resolve, reject);
        });
    });
}
复制代码

须要注意的是all和race函数中的数组成员不必定是Promise对象,若是不是Promise提供了resolve方法将其转化成Promise对象。resolve的实现很简单以下:

Promise.resolve = function (value) {
    return new Promise(resolve => {
        resolve(value);
    });
}
复制代码

至此一个比较规范的Promise实现了。

参考

《ES6入门-阮一峰》
《ES6 系列之咱们来聊聊 Promise》
《异步解决方案----Promise与Await》
《Promise原理讲解 && 实现一个Promise对象》
《面试精选之Promise》
《Promise/A+》
《Promise之你看得懂的Promise》
《八段代码完全掌握 Promise》
《Promise不会??看这里!!!史上最通俗易懂的Promise!!!》
《Promise 必知必会(十道题)》

相关文章
相关标签/搜索