阅读 166

ES6 Promise - 让我们解开的面纱(遵循Promise/A+规范)

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。ECMAscript 6 原生提供了 Promise 对象。而且ES7中的async/await也是Promise基础实现的。Promise到底有魅力和作用呢?本文将解开它的面纱,探索promise的原理及用法。不用再被一些回调地狱、各种异步调用所头疼烦恼。


什么是Promise?

     promise,是承诺的意思。在JavaScript中promise指一个的对象或函数(是一个包含了兼容promise规范then方法的对象或函数)。 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
  1. promise的特点

1-1、 Promise的三种状态   

  • pending: Promise对象实例创建时候的初始状态
  • resolved:可以理解为成功的状态
  • rejected:可以理解为失败的状态      
  1-2、 根据Promise/A+规范,可知
  • 如果是pending状态,则promise:可以转换到resolved或rejected状态。
  • 如果是resolved状态,则promise:不能转换成任何其它状态。必须有一个值,且这个值不能被改变。
  • 如果是rejected状态,则promise可以:不能转换成任何其它状态。必须有一个原因,且这个值不能被改变。
”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。


1-3、promise 有一个 then 方法,就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数 (onFulfilled),reject 时执行第二个函数(onRejected)

promiseFn().then(resolve(onFulfilled){
        //当promise状态变成fulfilled时,调用此函数
    },reject(onRejected){
        //当promise状态变成rejected时,调用此函数
    });复制代码
  •  resolve,reject 都是可选参数
  1.  如果onFulfilled不是一个函数,则忽略之。
  2. 如果onRejected不是一个函数,则忽略之。
  • 如果onFulfilled是一个函数:

    1. 它必须在promise fulfilled后调用, 且promise的value为其第一个参数。
    2. 它不能在promise fulfilled前调用。
    3. 不能被多次调用。
  • 如果onRejected是一个函数,

    1. 它必须在promise rejected后调用, 且promise的reason为其第一个参数。
    2. 它不能在promise rejected前调用。
    3. 不能被多次调用。
  • onFulfilledonRejected 只允许在 execution context 栈仅包含平台代码时运行. 
  • onFulfilledonRejected 必须被当做函数调用 (i.e. 即函数体内的 thisundefined). 
  • 对于一个promise,它的then方法可以调用多次.
    1. promise fulfilled后,所有onFulfilled都必须按照其注册顺序执行。
    2. promise rejected后,所有OnRejected都必须按照其注册顺序执行。
  • then 必须返回一个promise .

    promise2 = promise1.then(onFulfilled, onRejected);
    复制代码
    1. 如果onFulfilledonRejected 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x).
    2. 如果onFulfilledonRejected抛出了异常e, 则promise2应当以ereason被拒绝。
    3. 如果 onFulfilled 不是一个函数且promise1已经fulfilled,则promise2必须以promise1的值fulfilled.
    4. 如果 OnReject 不是一个函数且promise1已经rejected, 则promise2必须以相同的reason被拒绝.

 

2、为什么使用promise?有什么好处呢?

  • 2-1 对于回调函数:  可以解决回调地狱,如下图:         例如,使用jQuery的ajax多次向后台请求数据时,并且每个请求之间需要相互依赖,则需要回调函数嵌套来解决而形成“回调地狱”。

    $.get(url1, data1 => {
        console.log(data1,"第一次请求");
        $.get(data1.url, data2 => { // 第一次请求后的返回url 在此请求后台
            console.log(data2,"第二次请求")
            .....
        })
    })
    
    
    复制代码
这样一来,在处理越多的异步逻辑时,就需要越深的回调嵌套,
复制代码

这种编码模式的问题主要有以下几个: 

  1. 代码逻辑书写顺序与执行顺序不一致,不利于阅读与维护。
  2.  异步操作的顺序变更时,需要大规模的代码重构。
  3.  回调函数基本都是匿名函数,bug 追踪困难。 
  4. 回调函数是被第三方库代码(如上例中的 ajax )而非自己的业务代码所调用的,造成了 IoC 控制反转。
  5. 结果不能通过return返回

Promise 怎么解决呢?

let p = url1 => { 
    return new Promise((resolve, reject) => {
        $.get(url, data => {
            resolve(data)
        });
    })
};

// 
p(url).then(resvloe => {
    return p(resvloe.url);   
}).then(resvloe2 => {
    return resvloe2(resvloe2.url);
}).then(resvloe3 => {
    console.log(resvloe3);
}).catch(err => throw new Error(err));
当第一个then中返回一个promise,会将返回的promise的结果,传递到下一个then中。
这就是比较著名的链式调用了。   复制代码
  • 2-2、Promise 也有一些缺点。
  • 首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

3、Promise 使用

  • 1. 创建Promise。要想创建一个 promise 对象、可以使用 new 来调用 Promise 的构造器来进行实例化。接收一个excutor执行函数作为参数, excutor有两个函数类型形参resolve reject。

let promise = new Promise(function(resolve, reject) {
    // 异步处理
    // 处理结束后、调用resolve 或 reject
});
复制代码

  • 2.promise中的状态变化。
1. promise 对象初始化状态为 pending 
2. 当调用resolve(成功),会由pending => resolved 
3. 当调用reject(失败),会由pending => rejected
  • 3. promise对象方法 then方法
3-1.then方法  注册:对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。promise.then() 是 promise 最为常用的方法。

   // onFulfilled 是用来接收promise成功的值
  // onRejected 是用来接收promise失败的原因 
// then方法是异步的
promise.then(onFulfilled, onRejected);复制代码

3-2. resolve(成功): onFulfilled会被调用

let promise = new Promise((resolve, reject) => {
   resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilled
    console.log(result); // 'fulfilled' 
}, reason => { // onRejected 不会被调用
    
})
复制代码

3-3.reject(失败) onRejected会被调用

let promise = new Promise((resolve, reject) => {
   reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用
  
}, reason => { // onRejected 
    console.log(reason); // 'rejected'
})
复制代码

3-4.promise.catch 方法:捕捉错误 

(catch 方法是 promise.then(null, rejection) 的别名,用于指定发生错误时的回调函数。)复制代码

promise.catch(onRejected)
相当于
promise.then(null, onRrejected);

// 注意
// onRejected 不能捕获当前onFulfilled中的异常
promise.then(onFulfilled, onRrejected); 

// 可以写成:
promise.then(onFulfilled)
       .catch(onRrejected);   复制代码

3-5、promise chain  方法每次调用 都返回一个新的promise对象 所以可以链式写法
Promise的静态方法

function step1() {
    console.log("step1");
}
function step2() {
    console.log("step2");
}
function onRejected(error) {
    console.log("错误方法:", error);
}

var promise = Promise.resolve();
promise
    .then(step1)
    .then(step2)
    .catch(onRejected) // 捕获前面then方法中的异常复制代码

4、Promise的方法是使用

  1. Promise.resolve 返回一个fulfilled状态的promise对象
Promise.resolve('成功');
// 相当于
let promise = new Promise(resolve => {
   resolve('成功');
});
复制代码

//库中实现
Promise.resolve = function (val) {  return new Promise((resolve, reject) => resolve(val))}
复制代码

2.Promise.reject 返回一个rejected状态的promise对象

var p = Promise.reject('出错了');
 
p.then(null, function (s){
  console.log(s)
});
// 出错了
复制代码

3.Promise.all 接收一个promise对象数组为参数

只有全部为resolve才会调用 通常会用来处理 多个并行异步操作

const p1 = new Promise((resolve, reject) => {
    resolve(1);
});

const p2 = new Promise((resolve, reject) => {
    resolve(2);
});

const p3 = new Promise((resolve, reject) => {
    reject(3);
});

Promise.all([p1, p2, p3]).then(data => { 
    console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的
}, err => {
    console.log(err);
});
复制代码

//库中实现
Promise.all = function(arr) {    
 return new Promise((resolve, reject) => {  
      let num = 0,innerArr = [];
       function done(index,data){   
         innerArr[index] = data;    
        num ++;           
 if(num === arr.length){    
            resolve(innerArr); 
           }                   }
      for(let i =0 ;i<arr.length;i++){     
    arr[i].then((res)=>{    
       done(i,res);       
  },reject); // 有一个失败 就返回  
    }  
  })}
复制代码


4.Promise.race 接收一个promise对象数组为参数

Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

function timerPromisefy(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();

Promise.race([
    timerPromisefy(10),
    timerPromisefy(20),
    timerPromisefy(30)
]).then(function (values) {
    console.log(values); // 10
});
复制代码

// 库中实现
Promise.race = function(arr) {  
  return new Promise((resolve, reject) => {   
     arr.forEach((item, index) => {
            item.then(resolve, reject); 
       }); 
   });}
复制代码

5.Promise的finally

Promise.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};复制代码

5、prmoise 代码库实现


/** * Promise 实现 遵循promise/A+规范 * Promise/A+规范译文: * https://promisesaplus.com/ */

// 判断x是不是promise 根据规范
function resolvePromise(promise2, x, resolve, reject) {
    // 1、如果promise2 和 x 指向相同的值, 使用 TypeError做为原因将promise拒绝。 (就会导致循环引用报错)   
 if (promise2 === x) { 
       return reject(new TypeError('循环引用')); 
   }  
  // 避免多次调用   
 let isUsed = false;
    // 2、如果x是一个promise对象 (该判断和下面 判断是不是thenable(thenable 是一个包含了then方法的对象或函数)对象重复 所以可有可无)     
/**     * 如果x是pending状态,promise必须保持pending走到x resolved或rejected.        
如果x是resolved状态,将x的值用于resolve promise.        如果x是rejected状态, 将x的原因用于reject promise..  
   */  
  // if (x instanceof Promise) { // 获得它的终值 继续resolve   
 //     if (x.status === 'pending') { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值 递归   
 //         x.then(y => {   
 //             resolvePromise(promise2, y, resolve, reject); 
   //         }, reason => {  
  //             reject(reason);    
//         });    
//     } else { 
// 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise   
 //         x.then(resolve, reject); 
   //     }    
//    
 // 3、如果 x 为对象或者函数    
//     
/**    
//      *  1.将 then 赋为 x.then.  
   //         2.如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。  
  //         3.如果 then 是一个函数, 以x为this调用then函数, 且第一个参数是resolve,第二个参数是reject,且:
    //            3-1.当 resolve 被以 y为参数调用, 执行 [[Resolve]](promise, y).  
  //            3-2.当 reject 被以 reason 为参数调用, 则以reason为原因将promise拒绝。
    //            3-3.如果 resolvee 和 reject 都被调用了,或者被调用了多次,则只第一次有效,后面的忽略。
    //            3-4.如果在调用then时抛出了异常,则:  
  //                 如果 resolve 或 reject 已经被调用了,则忽略它。isUsed = true;  
  //                 否则, 以e为reason将 promise 拒绝。   
 //         4.如果 then不是一个函数,则 以x为值resolve promise。 
   //    
  */  
  // } else    
 if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try { // 是否是thenable对象(具有then方法的对象/函数)  如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
            let then = x.then;
            if (typeof then === 'function') { 
               then.call(x, y => {  // 如果y是promise就继续递归解析promise 
                   if (isUsed) return; 
                   isUsed = true; 
                   resolvePromise(promise2, y, resolve, reject);
                }, reason => {  // 只要失败了就失败了 不用再递归解析是都是promise   
                 if (isUsed) return; 
                   isUsed = true; 
                   reject(reason); 
               })
            } else { // 说明是一个函数,则 以x为值resolve promise。
                resolve(x); 
           }
        } catch (e) {   
         if (isUsed) return; 
           isUsed = true;
            reject(e);
        }
    } else {  //4、如果 x 不是对象也不是函数,则以x为值 resolve promise。例如 x = 123 或 x ='成功'
        resolve(x);
    }}


// Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
// Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

class Promise {    // Promise 是一个类, new Promise 返回一个 promise对象 接收一个ex执行函数作为参数, ex有两个函数类型形参resolve reject   
 /**    
 * var promise = new Promise(function(resolve, reject) { //会立即执行        // 异步处理      
  // 处理结束后、调用resolve 或 reject
   //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)        
//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.        
   setTimeout(function(){  
    resolve("成功!"); //代码正常执行!
        }, 250); 
     });
      promise.then(function(successMessage){
            //successMessage的值是上面调用resolve(...)方法传入的值. 
           //successMessage参数不一定非要是字符串类型,这里只是举个例子
            console.log("Yay! " + successMessage); 
       });    
      */    

constructor(ex) {  // 

  this.status = 'pending'; // 初始状态 (表示 未开始)
 this.resolveVal = undefined;  // resolved状态时(表示成功) 返回的信息
 this.rejectVal = undefined;  // rejected状态时(表示失败) 返回的信息
 this.onResolveCallBackFns = [];   // 存储resolved状态对应的onResolved函数 (因为可以链式调用可以多个then方法)
        this.onRejectCallBackFns = [];    // 存储rejected状态对应的onRejected函数

        /**    
* 一个Promise必须处在其中之一的状态:pending, fulfilled 或 rejected. 
        *          * 如果是pending状态,则promise:
            可以转换到resolved或rejected状态。
            如果是resolved状态,则promise:
            不能转换成任何其它状态。
            必须有一个值,且这个值不能被改变。
            如果是rejected状态,则promise可以:
            不能转换成任何其它状态。
            必须有一个原因,且这个值不能被改变。
            ”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。 
       */
        let resolve = (data) => {   // data 成功态时接收的终值 
           if (this.status === 'pending') { 
 // 只能由pedning状态 => resolved状态 避免调用多次resolve reject)  
              this.status = 'resolved';
                this.resolveVal = data;
                this.onResolveCallBackFns.forEach(cb => cb()); 
           } 
       }

        let reject = (err) => { 
           if (this.status === 'pending') {
  // 只能由pedning状态 => rejected状态 避免调用多次resolve reject)
                this.status = 'rejected'; 
               this.rejectVal = err; 
               this.onRejectCallBackFns.forEach(cb => cb());
            } 
       }
        // 捕获在ex执行器中抛出的异常
        // new Promise((resolve, reject) => {
        //     throw new Error('error in ex')
        // }) 
       try { 
           ex(resolve, reject); 
       } catch (e) { 
           reject(e);
        }
    }
    //  按照prmoise a+ 规范  then方法接受两个参数  then方法是异步执行的  必须返回一个promise    then(resolve, reject) {        //  then 方法 resolve,reject 都是可选参数 保证参数后续能够继续执行        //      
 1.1、  如果resolve不是一个函数,则忽略之。 如果reject不是一个函数,则忽略之。 
       resolve = typeof resolve == 'function' ? resolve : y => y;     
       reject = typeof reject == 'function' ? reject : err => { throw err };

        let promise2; // then必须返回一个promise
        if (this.status === 'pending') { // 等待态  
          // 当异步调用resolve/rejected时 将resolve/reject收集暂存到集合中          
            promise2 = new Promise((res, rej) => { 
               this.onResolveCallBackFns.push(() => {
                    setTimeout(() => { 
                       try {   
                         // resolvePromise可以解析x和promise2之间的关系                             /** 如果resolve 或 reject 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x).                            // 如果resolve 或 reject 抛出了异常e, 则promise2应当以e为rejectVal被拒绝。                            // 如果 resolve 不是一个函数且promise1已经resolved,则promise2必须以promise1的值resolved.                            // 如果 reject 不是一个函数且promise1已经rejected, 则promise2必须以相同的rejectVal被拒绝.                            */                            let x = resolve(this.resolveVal);                            resolvePromise(promise2, x, res, rej);                        } catch (e) {                            rej(e);                        }                    }, 0);                                   });

                this.onRejectCallBackFns.push(() => { 
                   setTimeout(() => { 
                       try { 
                           let x = reject(this.rejectVal); 
                           resolvePromise(promise2, x, res, rej); 
                       } catch (e) { 
                           rej(e);  
                      } 
                     }, 0); 
                     });   
         }); 
       }
        if (this.status == 'resolved') { 
  // 它必须在promise resolved后调用, 且promise的value为其第一个参数。
            promise2 = new Promise((res, rej) => {
                //  用setTimeout方法原因 : 
                //  1、方法是异步的
                 // 2、  对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 由于之前状态已经为resolved/rejected状态,则会走的下面逻辑),
                // 所以要确保为resolved/rejected状态后 也要异步执行resolve/reject 保持统一 
               setTimeout(() => { 
                   try { 
                      let x = resolve(this.resolveVal);
                        resolvePromise(promise2, x, res, rej); // 
 // resolvePromise可以解析x和promise2之间的关系 
                    } catch (e) { 
                       rej(e);  
                  }
                })
            })
        }
        if (this.status == 'rejected') {  // 必须在promise rejected后调用, 且promise的rejectVal为其第一个参数 
           promise2 = new Promise((res, rej) => {                //    方法是异步的  所以用setTimeout方法   
              setTimeout(() => {
                    try { 
                       let x = reject(this.rejectVal);  
                      resolvePromise(promise2, x, res, rej);   // resolvePromise可以解析x和promise2之间的关系  
                   } catch (e) { 
                       rej(e); 
                   }
                });
            })
        }
        return promise2; // 调用then后返回一个新的promise    }
    // catch接收的参数 只用错误 catch就是then的没有成功的简写 
   catch(err) { 
       return this.then(null, err);
    }}

/** * Promise.all Promise进行并行处理 
* 参数: arr对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。 
*/
Promise.all = function (arr) {
    return new Promise((resolve, reject) => {
        let num = 0, innerArr = [];
        function done(index, data) {
            innerArr[index] = data;
            num++; 
           if (num === arr.length) {
                resolve(innerArr); 
           }
        }
        for (let i = 0; i < arr.length; i++) { 
           arr[i].then((res) => {
                done(i, res); 
           }, reject); // 有一个失败 就返回 
       }    })}

/** * Promise.race 
* 参数: 接收 promise对象组成的数组作为参数 
* 返回值: 返回一个Promise实例 
* 只要有一个promise对象进入 resolved 或者 rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快) 
*/
Promise.race = function (arr) {
    return new Promise((resolve, reject) => { 
       arr.forEach((item, index) => {
            item.then(resolve, reject); 
       }); 
   });}
// Promise.reject 返回一个rejected状态的promise对象

Promise.resolve = function (val) {  
  return new Promise((resolve, reject) => resolve(val))}

// .Promise.resolve 返回一个fulfilled状态的promise对象
Promise.reject = function (val) {   
 return new Promise((resolve, reject) => reject(val));}

Promise.deferred = Promise.defer = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject; 
   }) 
   return dfd;}

module.exports = Promise;复制代码


6、测试

let p = new Promise((resolve, reject) => { 
 reject('err');})
p.then().then().catch(r => {  
console.log(r);}).
then(data => {  
console.log('data', data);})

执行结果:
errdata undefined
复制代码

let fs = require('fs');
function read() { 
 // 好处就是解决嵌套问题  
// 坏处错误处理不方便了 
 let defer = Promise.defer(); 
 fs.readFile('./1.txt', 'utf8', (err, data) => { 
   if (err) defer.reject(err); 
   defer.resolve(data)  }); 
 return defer.promise;}
read().then(data => {  
console.log(data);});

执行结果
我是1.txt内容复制代码


文章分类
前端