Promise的实现

111 阅读8分钟

为什么会有promise?

JavaScript一大特点就是单线程,为了不阻塞主线程,有些耗时操作(比如ajax)必须放在任务队列中异步执行。传统的异步编程解决方案之一回调,很容易产生回调地狱问题。

什么是回调地狱呢?

  1. 多层嵌套的问题。
  2. 每种任务的处理结果存在两种可能性(成功或失败),那么需要在每种任务执行结束后分别处理这两种可能性。

在实际的使用当中,有非常多的应用场景我们不能立即知道应该如何继续往下执行。最重要也是最主要的一个场景就是ajax请求。通俗来说,由于网速的不同,可能你得到返回值的时间也是不同的,这个时候我们就需要等待,结果出来了之后才知道怎么样继续下去。

//简单的ajax原生实现:   
 var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10'; 
   var result;
   var XHR = new XMLHttpRequest();
    XHR.open('GET',url,true);
    XHR.send();
    XHR.onreadystatechange = function(){
      if(XHR.readyState == 4 && XHR.status == 200){
        result = XHR.response;
        console.log(result); 
     }
    }

在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到想要的数据,之后才能开始处理数据。

这样做看上去并没有什么麻烦,但如果这个时候,我们还需要另外一个ajax请求,这个新ajax请求的其中一个参数,得从上一个ajax请求中获取,这个时候我们就不得不等待上一个接口请求完成之后,再请求后一个接口。如下:

//简单的ajax原生实现:

var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';    
var result;
var XHR = new XMLHttpRequest();
XHR.open('GET',url,true);    
XHR.send();    
XHR.onreadystatechange = function(){      
if(XHR.readyState == 4 && XHR.status == 200){        
result = XHR.response;        
console.log(result);        
//伪代码        
var url2 = 'http:xxx.yyy.com/zzz?ddd=' + result.someParams;        
var XHR2 = new XMLHttpRequest();        
XHR2.open('GET',url,true);        
XHR2.send();        
XHR2.onreadystatechange = function(){          
...        }      }    
}

当出现第三个ajax,甚至更多的时候,仍然依赖上一个请求时,我们的代码就变成了一场灾难,也就是回调地狱;

在模块化过程中,为了代码有可读性和可维护性,需要把数据请求与数据处理区分开;

如何解决回调地狱?--可以用promise来解决;

promise的基础知识:

  • (1)promise对象有三种状态,分别是:

pending:等待中,或者进行中,表示还没有得到结果;

resolve(Fulfilled):已经完成,表示得到了我们想要的结果,可以继续往下执行;

rejected:也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行;

  • 这三种状态不受外界的影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆;
  • Promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个Promise,同一个Promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致。
  • then方法接受两个参数,第一个参数是成功时的回调,在Promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在Promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个Promise传入,也接受一个“类then”的对象或方法,即thenable对象。ajax就是一个thenable对象。

在Promise对象的构造函数中,将一个函数作为第一个参数。而这个函数,就是用来处理Promise的状态变化。

new Promise(function(resolve, reject) {    
if(true) { 
resolve() 
};    
if(false) { 
reject() 
};
})

reject和resolve都是一个函数,作用分别是将状态修改为resolved和reject;

(2)Promise对象中的then()方法

可以接收构造函数中处理的状态变化,并分别对应执行,then方法有两个参数,第一个函数接收resolve状态的执行,第二个参数接收reject状态的执行;then方法的执行结果也会返回一个Promise对象;因此可以进行then的链式执行,是解决回调地狱的主要方式;

(3)Promise中的数据传递

var fn =function(num) { 
   return new Promise(function(resolve,reject) {
        if (typeof num == 'number') {
            resolve(num);
        } else {
            reject('TypeError'); 
       }
    })} fn(2).then(function(num)
{
    console.log('first: ' + num);    
    return num + 1;
}).then(function(num)
{ 
   console.log('second: ' + num);    
    return num + 1;
}).then(function(num)
{ 
   console.log('third: ' + num);
    return num + 1;
}); // 输出结果first: 2second: 3third: 4

promise对于ajax的封装:

var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10'; // 封装一个get请求的方法function
getJSON(url) {    
return new Promise(function(resolve,reject) {        
var XHR = new XMLHttpRequest();        
XHR.open('GET', url, true);        
XHR.send();         
XHR.onreadystatechange = function() {            
if (XHR.readyState == 4) {                
if (XHR.status == 200) {                    
try {                        
var response =JSON.parse(XHR.responseText);                        
resolve(response);                    
} catch (e) {                        
reject(e);                    
}                
} else {                    
reject(new Error(XHR.statusText));                
}            
}        
}    
})} 
getJSON(url).then(resp=> console.log(resp));

promise的缺点:无法取消Promise,一旦新建它就会立即执行,无法中途取消;其次,如果不设置回调函数,Promise内部抛出的错误就不会反应到外部;第三。当处于pending状态时,无法得知目前进展到哪一个阶段(是刚刚开始还是即将完成)

4.promise.all

当有一个ajax请求时,它的参数需要另外两个甚至更多请求都有返回结果之后才能确定,那么这个时候就需要用到promise.all;

Promise.all接收一个Promise对象组成的数组作为参数,只有当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,才会去调用then方法;

5.promise.race

与promise.all相似,promise.rance都是以一个Promise对象组成的数组作为参数,不同的是,只有当数组中的其中一个Promise状态变成resolved或者rejected的时候,就可以调用.then方法了;

6.promise如何手动实现?

// 三种状态
const PENDING= "pending";
const RESOLVED= "resolved";
const REJECTED= "rejected";
// promise 接收一个函数参数,该函数会立即执行
function MyPromise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  // 用于保存 then 中的回调,只有当
promise
  // 状态为 pending
时才会缓存,并且每个实例至多缓存一个
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];_this.resolve = function (value) {
    if (value instanceof MyPromise)
{
      // 如果 value 是个
Promise,递归执行
      return value.then(_this.resolve, _this.reject)
    }
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState ===
PENDING)
{
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };_this.reject = function (reason) {
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState ===
PENDING)
{
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }
  // 用于解决以下问题
  // new Promise(() => throw
Error('error))
  try {
    fn(_this.resolve, _this.reject);
  } catch (e) {
    _this.reject(e);
  }
}MyPromise.prototype.then = function (onResolved,
onRejected) {
  var self = this;
  // 规范 2.2.7,then 必须返回一个新的
promise
  var promise2;
  // 规范 2.2.onResolved 和 onRejected
都为可选参数
  // 如果类型不是函数需要忽略,同时也实现了透传
  //
Promise.resolve(4).then().then((value) => console.log(value))
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      // 规范 2.2.4,保证
onFulfilled,onRjected 异步执行
      // 所以用了 setTimeout 包裹下
      setTimeout(function () {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        // 异步执行onRejected
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }if (self.currentState === PENDING) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      self.resolvedCallbacks.push(function ()
{
        // 考虑到可能会有报错,所以使用 try/catch
包裹
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

self.rejectedCallbacks.push(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 规范 2.3.1,x 不能和 promise2相同,避免循环引用
  if (promise2 === x) {
    return
reject(new TypeError("Error"));
  }
  // 规范 2.3.2
  // 如果 x 为 Promise,状态为 pending
需要继续等待否则执行
  if (x instanceof MyPromise)
{
    if (x.currentState === PENDING) {
      x.then(function (value) {
        // 再次调用该函数是为了确认 x resolve的
        // 参数是什么类型,如果是基本类型就再次resolve
        // 把值传给下个 then
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }
  // 规范 2.3.3.3.3
  // reject 或者 resolve其中一个执行过得话,忽略其他的
  let called = false;
  // 规范 2.3.3,判断 x 是否为对象或者函数
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    // 规范 2.3.3.2,如果不能取出 then,就
reject
    try {
      // 规范 2.3.3.1
      let then = x.then;
      // 如果 then 是函数,调用 x.then
      if (typeof then === "function")
{
        // 规范 2.3.3.3
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            // 规范 2.3.3.3.1
            resolutionProcedure(promise2,
y, resolve, reject);
          },
          e => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        // 规范 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 规范 2.3.4,x 为基本类型
    resolve(x);
  }
}

7.实现promise.all和promise.race

  • promise.all

promise.all([p1,p2,p3])中,all方法的参数对象:

  • 必须是数组
  • 可以传入非promise,all自动将其转换为promise对象
  • 传入的值必须按顺序输出
  • 一旦又一个reject则状态立马变为reject,并将错误原因抛出

function myPromiseAll(promiseArr){
    // 为了让传入的值不是promise也返回promise
    return new Promise((resolve,reject) => {
        if(!Array.isArray(promiseArr)){
            throw('promiseArr必须为数组')
        }
        let resArr = []
        let len = promiseArr.length;
        let count = 0;
        for(let i = 0; i < len; i++){
            // Promise.resolve将数组中非promise转为promise
            Promise.resolve(promiseArr[i])
            .then(value => {
                count++
                resArr[i] = value
                
                if(count == len) return resolve(resArr)
            })
            .catch(err => {
                return reject(err)
            })
           
        }
    })
    
}

let p1 = Promise.resolve(1)
let p2 = Promise.resolve(2)
let p3 = Promise.resolve(3)

myPromiseAll([p1,p3,p2]).then(res => {
    console.log(res); // [1,3,2]
    
})

  • promise.race

只要有一个 promise 执行完,直接 resolve 并停止执行;

function myPromiseRace(promiseArr){
    return new Promise((resolve,reject) => {
        if(!Array.isArray(promiseArr)) throw('参数必须为数组')
        let len = promiseArr.length
        for(let i = 0; i < len; i++){
            Promise.resolve(promiseArr[i]).then(val => {
                resolve(val)
            }).catch(err => {
                reject(err)
            })
        }
        
    })
}

8.promise的弊端

1.延时问题(涉及到evnet loop)

2.promise一旦创建,无法取消

3.pending状态的时候,无法得知进展到哪一步(比如接口超时,可以借助race方法)

4.promise会吞掉内部抛出的错误,不会反映到外部。如果最后一个then方法里出现错误,无法发现。(可以采取hack形式,在promise构造函数中判断onRejectedCb的数组长度,如果为0,就是没有注册回调,这个时候就抛出错误,某些库实现done方法,它不会返回一个promise对象,且在done()中未经处理的异常不会被promise实例所捕获)

5.then方法每次调用都会创建一个新的promise对象,一定程度上造成了内存的浪费


参考

juejin.cn/post/684490…

blog.csdn.net/qq149898227…

blog.poetries.top/FE-Intervie…

www.jianshu.com/p/27735abb9…