传统的异步回调编程最大的缺陷是:回调地狱,由于业务逻辑非常复杂,代码串行请求好几层;并行请求以前也要通过引用step、async库实现。现在ES6推出了Promise,通过Promise的链式调用可以解决回调地狱问题,通过Promise.all方法可以解决并行请求的问题。现在我们通过手写Promise来彻底理解Promise的原理。
 一、构造简单Promise函数,实现Promise的then方法
先来看一下简单的使用方法:
var promise=new Promise(function(resolved,rejected){
  console.log(1);
  resolved('123');
})
promise.then(function(data){
  console.log('success'+data)
},function(err){
  console.log('fail'+err)
})

  

注意点:
1. new Promise传入的executor方法代码是同步执行的;
2. promise对象的状态有三种:pending(等待态),resolved(成功态),rejected(失败态),只能从等待态转化成成功态或失败态;
3. executor中执行resolve()方法,表示将转化为成功态,promise.then调用时执行成功的方法,executor中执行reject()方法,表示将转化为成功态,promise.then调用时执行失败的方法。
思路:
1. 首先构造一个类Promise,传入一个参数executor方法;
2. 用status记录Promise状态,默认是pending,成功态是resolved,失败态是rejected, execuotor执行时,重写resolve、reject方法,执行到这两个方法时,将promise状态修改,并将参数存储起来,以便then方法调用;
3.当实例执行then方法时,依据promise状态来执行成功方法或失败方法。
这样就实现了这个简单的peomise,代码如下:
function Promise(executor){
  let self=this;
  self.status='pending';
  self.value=undefined;
  self.reason=undefined;
function resolve(value){
  if(self.status==='pending'){
    self.value=value;
    self.status='resolved';
  }
}
function reject(reason){
  if(self.status==='pending'){
    self.reason=reason;
    self.status='rejected';
  }
}
  try{
    executor(resolve,reject) //第一步:先执行executor,根据里面resolve和reject的调用,执行上面的resolve和reject方法
  }catch(e){
    reject(e);
  }
}
Promise.prototype.then=function(onFulfilled,onRejected){
  let self=this;//第二步:then被调用的时候根据已经记录的promise状态,执行成功方法或失败方法
  if(self.status==='resolved'){
    onFulfilled(self.value);
  }
  if(self.status==='rejected'){
    onRejected(self.reason);
  }
}
module.exports = Promise
二、实现Promise executor中的异步调用
先来看一下简单使用小例子:
var promise=new Promise(function(resolve,reject){
console.log('hello')
setTimeout(function(){
resolve('ok')
},1000);
})
promise.then(function(data){
console.log('success'+data)
},function(err){
console.log('fail'+err)
})
上面例子,先打印出hello,过1秒后执行resolve方法,打印出successok。
注意点:
1. executor执行一秒之后再调用resolve方法,才会执行then中的成功方法;
思路:
1. executor中方法没调用resolve之前,Promise方法一直是pending状态,这时候执行器已经执行完了then方法,这时我们把then方法中的成功方法和失败方法存入数组,当resolve方法调用时,去执行这些存储起来的方法;
2. 没有异步调用的话,executor执行时执行resolve方法,成功数组 为空,所以不会被影响,还是在then方法中去执行成功或失败方法。
 
function Promise(executor){
  let self=this;
  self.status='pending';
  self.value=undefined;
  self.reason=undefined;
  self.onResolvedCallbacks=[];
  self.onRejectedCallbacks=[];
  function resolve(value){
    if(self.status==='pending'){
      self.value=value;
      self.status='resolved';
      self.onResolvedCallbacks.forEach(item=>item());//最终执行resolve方法时,修改状态,将成功数组中的方法取出,一一执行。
    }
  }
  function reject(reason){
    if(self.status==='pending'){
      self.reason=reason;
      self.status='rejected';
      self.onRejectedCallbacks.forEach(item=>item());//最终执行reject方法时,修改状态,将失败数组中的方法取出,一一执行。
    }
  }
  try{
    executor(resolve,reject)
  }catch(e){
    reject(e);
  }
}
Promise.prototype.then=function(onFulfilled,onRejected){
  let self=this;
  if(self.status==='resolved'){
    onFulfilled(self.value);
  }
  if(self.status==='rejected'){
    onRejected(self.reason);
  }
  if(self.status==='pending'){ //执行then的时候是等待态,就将成功方法存入onResolvedCallbacks数组,将失败方法存入onRejectedCallbacks数组
    self.onResolvedCallbacks.push(function(){
      onFulfilled(self.value);
    });
    self.onRejectedCallbacks.push(function(){
      onRejected(self.reason);
    });
  }
}
module.exports = Promise
三、实现Promise then的链式调用
链式调用是Promise中的核心方法,也是最重要的一个方法,先来看一下链式调用的用法:
let p=new Promise((resolve,reject)=>{
  console.log(1);
  setTimeout(function(){
    resolve('123');
  },1000);
})
let p2=p.then((data)=>{
  console.log(data);
  return new Promise((resolve,reject)=>{
    setTimeout(function(){
      resolve('456');
    },1000);
  });
},err=>{
  console.log(err);
  throw Error('失败');
}).then((data)=>{
  console.log('aaa'+data);
},(err)=>{
  console.log(err);
})
上面代码首先打印出executor中的1,1秒之后执行resolve方法执行第一个then中的成功方法,先打印出123,再执行此方法中return的Promise,一秒之后执行下一个then的成功方法,打印出aaa456。
注意点:
1. 如果then方法返回一个普通值,执行下一个then的成功方法;
2. 如果抛出一个错误,执行下一个then的失败方法;
3. then方法中返回一个Promise对象,等待Promise对象执行成功就调用下个then的成功方法,这个Promise调用reject()就执行下一个then的失败方法;
思路:
1.在then方法后之所以可以接着调用then方法,肯定then方法需要返回一个Promise实例;
2. 在then方法中我们用x去接收成功或失败方法的返回值,当成功或失败方法抛出错误,直接执行reject,调用下一个then的失败方法,专门写了一个方法resolvePromise分析返回值;
3. resolvePromise方法中传入了四个参数,then中的Promise2,x值;
4. 如果x与promise相等,报类型错误循环引用;
5. 当x不是null,是个对象类型或者是个函数类型,并且他的then方法也是函数类型时,我们认为x是一个promise实例,执行x.then()方法;再用resolvePromise方法分析他的成功的返回值;
6. 如果x不是null不是object或function,我们认为x是一个普通值,执行promise2中的resolve方法;
7. 如果x.then方法获取失败,调用promise2的reject方法;
8. 定义一个called为true,当执行过resolve或reject方法后将其变为false,保证resolve和reject方法只执行一次;
9. 这里面存在两个递归调用,一个是then方法里面return new Promise方法,一个是resolvePromise方法,第一个的递归调用解决了executor中异步调用的问题,第二个递归调用解决了return Promise实例的问题。
代码如下:
 
function Promise(executor){
  let self=this;
  self.status='pending';
  self.value=undefined;
  self.reason=undefined;
  self.onResolvedCallbacks = []; // 存放then成功的回调
  self.onRejectedCallbacks = []; // 存放then失败的回调   function resolve(value){
    if(self.status==='pending'){ //这里面的self不能用this替代,因为是回调执行的时候不是在类里面所以这里的this不是类函数
      self.status='resolve';
      self.value=value;
      self.onResolvedCallbacks.forEach(item=>{item()})
    }
  }
  function reject(reason){
    if(self.status==='pending'){
      self.status='reject';
      self.reason=reason;
      self.onRejectedCallbacks.forEach(item=>{item()})
    }
  }
  try{
    executor(resolve,reject);
  }catch(e){
    reject(e);
  }
}
function resolvePromise(promise2,x,resolve,reject){
  if(promise2===x){//自己等于自己,报类型错误
    return reject(new TypeError('循环引用了'));
  }
  let called;//控制成功或失败只能调用一次
  if(x!==null && (typeof x==='object'||typeof x==='function')){
    try{
      let then=x.then;
      if(typeof then==='function'){
        then.call(x,function(y){ //y可能还是一个promise,递归解析,直到返回普通值
          if(called)return;
          called=true;
          resolvePromise(promise2,y,resolve,reject)
        },function(err){
          if(called)return;
          called=true;
          reject(err);
        });
      }else{
        resolve(x);
      }
    } catch (e) {
      if (called) return
      called = true;
      reject(e);
    }
  } else { // 说明是一个普通值
    resolve(x);
  }
}
Promise.prototype.then=function(onFufilled,onReject){
  onFufilled=typeof onFufilled==='function'?onFufilled:function(value){
    return value;
  }
  onReject=typeof onReject==='function'?onReject:function(err){
    throw err;//抛出错误,才会走下个then的失败
  }
  let self=this;
  let promise2;
  if(self.status=='pending'){
    promise2=new Promise(function(resolve,reject){
      self.onResolvedCallbacks.push(function(){
        setTimeout(function(){
          try{
            let x=onFufilled(self.value);
            resolvePromise(promise2,x,resolve,reject);
          }catch(e){
            reject(e)
          }
        })
      });
      self.onRejectedCallbacks.push(function(){
        setTimeout(function(){
          try{
            let x=onReject(self.reason);
            resolvePromise(promise2,x,resolve,reject);
          }catch(e){
            reject(e);
          }
        })
      });
    })
  }
  if(self.status=='resolve'){
    promise2=new Promise(function(resolve,reject){
    setTimeout(function(){
      try{
        let x=onFufilled(self.value);
        resolvePromise(promise2,x,resolve,reject);
      }catch(e){
        reject(e)
      }
    })
  })
}
  if(self.status=='reject'){
    promise2=new Promise(function(resolve,reject){
      setTimeout(function(){
        try{
          let x=onReject(self.reason);//这里的x可能是一个普通值,也可能是一个promise,所以写个方法统一处理。
          resolvePromise(promise2,x,resolve,reject);
        }catch(e){
          reject(e);
        }
      })
    })
  }
  return promise2;
};
四、检测一下我们的Promise是否符合Promise A+规范
我们需要先给Promise类增加一个defer方法,其实就是一个语法糖。
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
 
没有上面的语法糖,无法被检测。
下载一个插件npm install -g promises-aplus-tests
在命令行工具中执行命令:npm install -g promises-aplus-tests ./promise.js
即可检验自己的promise写的如何。
五、附赠Promise的all、race、resolve、reject、catch方法
 
Promise.all=function(promises){
//promises是一个promise的数组,同时需要返回一个promise
  return new Promise(function(resolve,reject){
    let arr=[]; //arr是最终返回值的结果集
    let i=0;
    function processData(index,y){
      arr[index]=y;
      if(++i===promises.length){
        resolve(arr);
      }
    }
    for(let i=0;i<promises.length;i++){ //因为then的时候,for循环已经走了,i值就是最大的,所以这里用let,保证每次i都是都是当前值
      promises[i].then(function(y){
        processData(i,y);
      },function(err){
        reject();
      })
    }
  })
}
Promise.race=function(promises){
  return new Promise(function(resolve,reject){
    for(let i=0;i<promises.length;i++){
      promises[i].then(function(data){
        resolve(data);
      },function(err){
        reject(err);
      })
    }
  })
}
Promise.resolve=function(value){
  return new Promise(function(resolve,reject){
    resolve(value);
  })
}
Promise.reject=function(reason){
  return new Promise(function(resolve,reject){
    reject(reason);
  })
}
 
Promise.prototype.catch = function (callback) {
  return this.then(null, callback)
}

Promise原理剖析的更多相关文章

  1. 30分钟,让你彻底明白Promise原理

    前言 前一阵子记录了promise的一些常规用法,这篇文章再深入一个层次,来分析分析promise的这种规则机制是如何实现的.ps:本文适合已经对promise的用法有所了解的人阅读,如果对其用法还不 ...

  2. Promise 原理

    异步:可同时好几件事,互不影响: 同步:按循序一件一件.... 异步好多缺点:.... promise就是解决异步计算的这些缺点的,主要用于: 1.异步计算: 2.可以将异步操作队列化  按期望的顺序 ...

  3. 原理剖析-Netty之服务端启动工作原理分析(下)

    一.大致介绍 1.由于篇幅过长难以发布,所以本章节接着上一节来的,上一章节为[原理剖析(第 010 篇)Netty之服务端启动工作原理分析(上)]: 2.那么本章节就继续分析Netty的服务端启动,分 ...

  4. 10分钟,让你彻底明白Promise原理

    什么是Promise?本代码用定外卖来举例子,让你明白. // 定外卖就是一个Promise,Promist的意思就是承诺// 我们定完外卖,饭不会立即到我们手中// 这时候我们和商家就要达成一个承诺 ...

  5. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  6. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  7. 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】

    原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...

  8. 【Xamarin 跨平台机制原理剖析】

    原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...

  9. iPhone/Mac Objective-C内存管理教程和原理剖析

    http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...

随机推荐

  1. HDU1080(DP)

    我用的dp是n^3的, dp[i][j] 表示在s串的i个前和t串的j个前,s[i],t[j]为最末端的两个串得到的最大值. 状态转移方程为: 之前将s和t串最尾端添加'-' ;i<=n;i++ ...

  2. 【BZOJ3195】[Jxoi2012]奇怪的道路 状压DP

    [BZOJ3195][Jxoi2012]奇怪的道路 Description 小宇从历史书上了解到一个古老的文明.这个文明在各个方面高度发达,交通方面也不例外.考古学家已经知道,这个文明在全盛时期有n座 ...

  3. [SharePoint 2010] 如何在小組網站內頁面上撥放影片或是音效檔

    在SharePoint 2010中, 我們可以像是Youtube一樣在網頁上撥放影片或是音效檔案. 影片或音效是採取串流的方式來撥放. 也就是說我們不需要把整個檔案都下載回來才開始撥放. 點選沒多久我 ...

  4. where both the key and the value are strings

    Environment Variables (The Java™ Tutorials > Essential Classes > The Platform Environment) htt ...

  5. Yii框架2.0的模块

    模块是个独立的软件单元,也是又控制器,视图,模型组成的,也可以有自己的布局.但他必须属于某个应用下,不能独立存在. 其中模块的控制器,视图和应用的控制器和视图使用基本相同,不作赘述,下面说说模块的使用 ...

  6. ActiveMQ 详解

    1. 如何同步索引库 方案一: 在taotao-manager中,添加商品的业务逻辑中,添加一个同步索引库的业务逻辑; 缺点:业务逻辑耦合度高,业务拆分不明确; 方案二: 业务逻辑在taotato-s ...

  7. LeetCode_Insertion Sort List

    题目:Sort a linked list using insertion sort,即仿照插入排序(直接插入排序)对一个链表排序. 插入排序的思想:总共进行n-1趟排序,在排列第i个元素时,前面的i ...

  8. Python并行编程(七):线程同步之事件

    1.基本概念 事件是线程之间用于通讯的对象.有的线程等待信号,有的线程发出信号.基本上事件对象都会维护一个内部变量,可以通过set方法设置为true,也可以通过clear方法设置为false.wait ...

  9. 需求用例分析之五:业务用例之Rational系

    版权声明:作者:张克强.未经作者允许不得转载. https://blog.csdn.net/zhangmike/article/details/28134897 作者:张克强    作者微博:张克强- ...

  10. facebook工具xhprof的安装与使用-分析php执行性能(转载)

    下载源码包的网址 http://pecl.php.net/package/xhprof