ES6版Promise实现,给你不一样的体验

栏目: JavaScript · 发布时间: 6年前

内容简介:摘要: 在很久很久以前,友情提醒: 本文使用ES6实现的就拿
ES6版Promise实现,给你不一样的体验

ES6版Promise实现,给你不一样的体验

摘要: 在很久很久以前, Promise 还没有来到这个世上。那时森林里的有这样一群攻城狮,他们饱受回调地狱(回调金字塔)的摧残,苦不堪言。直到有一天,一位不愿意留下姓名的特工横空出世,将他们从回调地狱中解救了出来,代号 Promise 。自此,很多人都踏上了寻找 Promise 的道路,我亦如此...

ES6版Promise实现,给你不一样的体验

友情提醒: 本文使用ES6实现的 Promise ,不会的童鞋们请自行脑补!What?这位同学你竟然不知道ES6,好的,放学了请不要走,我们单独交流一下......

回调地狱 VS Promise

就拿 fs (node的核心包)来说吧,假如我们需要同时请求 a.txtb.txt 中的数据,然后对数据进行操作。这种需求在我们的开发中也经常遇到哦!

  • 曾经的回调地狱
let fs = require('fs');
let arr = [];
fs.readFile('a.txt','utf8',function(err,data){
  arr.push(data);
  fs.readFile('b.txt','utf8',function(err,data){
    arr.push(data);
    // 如果需要把更多的文件数据,那滋味不敢想象
    console.log(arr); 
  })
})
复制代码
  • 现在的Promise
let fs = require('fs');
function read(url,coding){ // 首先我们对fs.readFile()进行promise封装
  return new Promise((resolve,reject)=>{
    fs.readFile(url,coding,function(err,data){
      if(err) reject(err);
      resolve(data);
    })
  })
}
Promise.all([read('a.txt','utf8'),read('b.txt','utf8')]).then(data=>{
  // 这里我们就可以直接操作请求到的两个文件的数据了,Promise还很贴心的返回了一个数组
  console.log(data);  
})
复制代码

相比较之下, Promise 和回调地狱的战争起初就不是一个等级的呀,回调地狱听起来强大,但实则一点不经揍啊! Promise 此时的内心应该是这样的:

ES6版Promise实现,给你不一样的体验

Promise之自己实现

看到这里,相信大家都很想知道Promise的核心实现是什么?接下来,请小伙伴们不要闭眼,看这里,看这里!我便说说我是如何在寻找 Promise 的道路上一条道走到黑的。(这标题起的,笑出猪叫声)

1、Promise 类封装

起初,我发现Promise是可以被new的,说明Promise 的出身是一个类啊,这可是一条很有价值的线索啊。(大家都知道,还用你说)

class Promise {
  constructor(executor) { // executor是new Promise的参数
    this.status = 'pending'; // 保存状态
    this.reason = undefined; // 失败的原因
    this.value = undefined; // 成功的结果
    let resolve = (value)=> {
      if(this.status === 'pending'){
        this.status = 'resolved';
        this.value = value;
      }
    }
    let reject = (reason)=>{
      if(this.status === 'pending'){
        this.status = 'rejected';
        this.reason = reason;
      }
    }
    try {
      executor(resolve, reject); // 执行器       
    } catch (e) {
      reject(e);
    }
  }
  // 定义实例上的then方法,then调用的时候都是异步调用的 
  then(onFulfilled, onRejected) {
    if(this.status === 'resolved'){ // status状态改变时执行onFulFilled
      onFulfilled(this.value);
    }
    if(this.status === 'rejected'){ // status状态改变时执行onFulFilled
      onRejected(this.reason);
    }
  }
}  
复制代码

这怎么仅仅一条线索就写出来这么东东呀,真让人摸不着头脑!别急,听我慢慢道来:

  • executor :执行器,默认是new的时候就自动执行,executor的执行是同步的,为什么要try一下呢,executor执行时如果 throw new Error('error') ,直接走reject
  • resolve, reject :定义了 executorresolve 成功的回调函数和 reject 失败的回调函数两个参数
  • reason,value :分别代表了成功返回的值和失败的原因
  • status :保存了 Promise 的三种状态 pending (等待态), fulfilled (成功态), rejected (失败态)
    • 当一个 promise 的状态处于 pending 时,它可以过渡到 fulfilled 或者 ejected
    • 当一个promise的状态处于 fulfilled 时或者 rejected 时,不能再过渡到其他任何状态
  • then 函数: 因 Promise 是可以链式调用的,说明 then 函数是定义在 Promise 类的原型 Prototype 上的。

这样我们就成功处理了同步情况下的 Promise ,是不是觉得自己已经追寻到 Promise 的终极力量了呢。(抽根烟,平复下躁动的心情)

ES6版Promise实现,给你不一样的体验

2、Promise异步的实现

在我们平时的开发中,往往异步代码比较多,异步执行需要,然 而Promiseexecutor 执行器又是同步执行的,它不等我们怎么办呢,好着急有木有。 我们在上面代码的基础上新增如下几行代码:

class Promise {
  constructor(executor) {
    this.onResolvedCallbacks = []; // 保存成功的回调
    this.onRejectedCallbacks = []; // 保存失败的回调
    let resolve = (value)=> {
      if(this.status === 'pending'){
        this.status = 'resolved';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    }
    let reject = (reason)=>{
      if(this.status === 'pending'){
        this.status = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }
  }
  then(onFulfilled, onRejected) { 
    if(this.status === 'pending'){
      this.onResolvedCallbacks.push(()=>{
          onFulfilled(this.value);
        });
      this.onRejectedCallbacks.push(()=>{
          onRejected(this.reason);
        });
      });
    }
  }
}
复制代码

当出现异步代码时, status 的状态还是 pending ,我们可以先把 then 函数中成功和失败的回调保存下来,等到异步代码执行完成后, status 的状态改变了,我们再去依次执行保存下来的回调函数。

看到这里,如果觉得自己已经基本掌握 Promise 的实现,只能说尔等对 Promise 的核心力量一无所知。(别废话,赶紧写)好的,各位大佬!

ES6版Promise实现,给你不一样的体验

3、Promise之链式调用的实现

在开始实现之前呢,我们先来看一下如下代码:

//  这里的Promise是ES6封装好的,并不是我们自己实现的 
let promise = new Promise((resolve,reject)=>{ 
  resolve('123');
})
let promise2 = promise.then((data)=>{
  throw new Error('error');
})
promise2.then((data)=>{
  console.log(data);
},(err)=>{
  console.log(err); // 这里输出了error
})
复制代码

上面代码说明 then 函数执行后返回的 promise2 实例并不是 promise 实例,说明返回值不是 this ,因为 promise 不能即调用成功后不能再走失败,所以 then 函数执行后返回的 promise2 是一个新的 promise 实例。(跟jQuery的链式调用不一样哦)

Promiseconstructor 的代码不需要改变,只需要对 then 函数进行再次封装:

then(onFulfilled, onRejected) {
    // onFulfilled和onRejected可能没传
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value=>value;
    onRejected = typeof onRejected === 'function' ? onRejected : (err)=>{throw err};
    let promise2; // 需要每次调用then方法时,都需要返回新的promise
    promise2 = new Promise((resolve, reject)=>{
      if(this.status === 'resolved'){
        setTimeout(()=>{
          try {
            let x = onFulfilled(this.value); 
            // 执行完当前回调后返回的结果可能还是个promise
            resolvePromise(promise2,x,resolve, reject);
          } catch (e) {
            reject(e);
          }
        },0)
      }
      if(this.status === 'rejected'){
        setTimeout(()=>{
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2,x,resolve, reject);
          } catch (e) {
            reject(e);
          }
        },0) 
      }
      if(this.status === 'pending'){
        this.onResolvedCallbacks.push(()=>{
          setTimeout(()=>{
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2,x,resolve, reject);
            } catch (e) {
              reject(e);
            }
          },0)
        });
        this.onRejectedCallbacks.push(()=>{
          setTimeout(()=>{
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2,x,resolve, reject);
            } catch (e) {
              reject(e);
            }
          },0)
        });
      }
    })
    return promise2;
  }
复制代码
  • onFulfilled,onRejected :当没有传的时候,需要做的处理
  • promise2then 函数的返回值是一个新的promise
  • setTimeout :Promise/A+规范(规范)要求 then 函数必须是异步的,当然原生的Promise实现并不是用的setTimeout,而是一个微任务
  • resolvePromise :封装 resolvePromise 方法,当then函数中的成功或者失败函数返回值x可能还是个promise

定义的resolvePromise方法:

let resolvePromise = (promise2,x,resolve, reject)=>{
  let called;
  // promise2和函数的返回值是同一个
  if(promise2 === x){
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }
  if(x!==null && (typeof x === 'object' || typeof x === 'function')){
    try {
      let then = x.then;
      if(typeof then === 'function'){
        then.call(x,(y)=>{
          if(called) return;
          called = true;
          resolvePromise(promise2,y,resolve, reject);// 递归处理,直到y是一个普通值
        },(err)=>{
          if(called) return;
          called = true;
          reject(err);
        })
      }else{ // then如果是一个常量
        if(called) return;
        called = true;
        resolve(x);
      }
    } catch (e) {
      if(called) return;
      called = true;
      reject(e);
    }
  }else{ // x如果是一个常量
    if(called) return;
    called = true;
    resolve(x);
  }
}
复制代码
  • 四个参数: promise2 (then函数的返回值,是一个新的promise) x (then中成功后者失败函数的返回值) resolve (promise2的resolve) reject (promise2的reject)
  • called : 加了 called 判断,防止多次调用,因为这里的逻辑不单单是自己的,还有别人的,别人的 promise 可能即会调用成功也会调用失败
  • let then = x.thenx 可能还是一个 promise ,那么就让这个 Promise 执行

至此,我们终于追寻到了 promise 的核心力量所在。来,让我们小小的庆贺一下:

ES6版Promise实现,给你不一样的体验

3、Promise之类上的方法实现

当然,我们已经初步了解了 promise 的核心力量,在我们开发的过程中,除了then方法,也会使用它的一些其他常用的方法,就像一位身经百战的特工,你除了会用刀,还要会用枪不是。我们在Promise类上定义它们:

static resolve(value){
    return new Promise((resolve,reject)=>{
      resolve(value);
    })
  }
  static reject(reason){
    return new Promise((resolve,reject)=>{
      reject(reason);
    })
  }
  static all(promises){
    return new Promise((resolve,reject)=>{
      let arr = [];
      let i = 0;
      let processData = (index,data)=>{
        arr[index] = data;
        if(++i === promises.length){
          resolve(arr);
        }
      }
      for(let i = 0; i< promises.length; i++){
        promises[i].then(data=>{
          processData(i,data);
        },reject);
      }
    })
  }
  static race(promises){
    return new Promise((resolve,reject)=>{
      for(let i = 0; i< promises.length; i++){
        promises[i].then(resolve,reject);
      }
    })
  }
  catch(onRejected){
    return this.then(null,onRejected);
  }
复制代码

相信resolve,reject,all,race这四个类上的方法和catch这个原型的方法大家都已经很熟悉了,我就不过多的啰嗦了。

因为,我实在是编不下去了,我还有更重要的事情要去做:

ES6版Promise实现,给你不一样的体验

结语: 花了很久写的这篇文章,如果这篇文章令你或多或少有些收获,请不要吝啬你的赞美(点个赞再走吗,小哥哥小姐姐),如果有写的不对的地方,也希望各位大佬能不吝指教,万分感谢!原创文章,转载请注明出处!


以上所述就是小编给大家介绍的《ES6版Promise实现,给你不一样的体验》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编写可读代码的艺术

编写可读代码的艺术

Boswell, D.、Foucher, T. / 尹哲、郑秀雯 / 机械工业出版社 / 2012-7-10 / 59.00元

细节决定成败,思路清晰、言简意赅的代码让程序员一目了然;而格式凌乱、拖沓冗长的代码让程序员一头雾水。除了可以正确运行以外,优秀的代码必须具备良好的可读性,编写的代码要使其他人能在最短的时间内理解才行。本书旨在强调代码对人的友好性和可读性。 本书关注编码的细节,总结了很多提高代码可读性的小技巧,看似都微不足道,但是对于整个软件系统的开发而言,它们与宏观的架构决策、设计思想、指导原则同样重要。编......一起来看看 《编写可读代码的艺术》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试