博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
实现Promise
阅读量:6932 次
发布时间:2019-06-27

本文共 8651 字,大约阅读时间需要 28 分钟。

使用Promise是极好的,它是如此有用以至于我觉得应该好好研究一下Promise,甚至是实现一个简易的版本。实现之前,我们先来看看Promise的用途:

使用Promise

callback hell

Promise的第一个用途是能够很好地解决的问题,假设要实现一个用户展示的任务,这个任务分为三步:

  1. 获取用户信息

  2. 获取用户图像

  3. 弹窗提示

不使用Promise,我们的实现可能是这样子:

getUserInfo(id, function (info) {  getUserImage(info.img, function () {    showTip();  })})

这里只是三步,如果有更长串的任务时,我们就会陷入到回调黑洞之中,为了解决这个问题,我们就可以使用Promise来处理这一长串任务,使用Promise的版本是这样子的:

// getUserInfo返回promisegetUserInfo(id)  .then(getUserImage)  .then(showTip)  .catch(function (e) {     console.log(e);  });

原来向右发展的代码,开始向下发展,这样也更适合编程习惯,如果要让我们的代码更加健壮,我们就需要在每一步来处理错误信息,使用promise这后,我们只需要在最后的catch中做善后处理。

并发

假如我们要显示某一个页的10条记录,但是我们只有一个通过id获取记录的接口,这样我们就需要发送10个请求,并且所有请求都完成之后再将记录全部添加到页面之中,Promise在这个场景下使用是特别合适的。

代码可能是这样子:

// ids要获取信息的所有记录id// getRecordById获取记录的接口,返回promisePromise.all(ids.map(getRecordById))  .then(showRecords)  .catch(function (e) {     console.log(e);  });

这就是Promise的一些简单的用途,当然令人兴奋的是Promise已经是ES6的标准,而且目前很多浏览器已经原生支持Promise了。对于那些无法使用Promise的浏览器,我们就只能自己去实现了,下面就来看看Promise的简单实现吧。

实现

warm up

先来盗用一张MDN的图,先来热热身,看看Promise的状态迁移:

promise

Promise有三种状态:

  1. pending:初始状态, 非 fulfilled 或 rejected

  2. fulfilled: 成功的操作

  3. rejected: 失败的操作

我们可以看出新建的Promise是pending状态,fulfill之后就会执行调用then的回调函数了,倘若reject了就会调用catch来进行异常处理了,并且无论是调用then还是catch都会返回新的promise,这就是为什么promise可以链式调用了。

接着,我们来研究一下规范是怎么描述

的。这里只抽取核心部分,边界问题不考虑。

构造函数:Promise ( executor )

  1. 检查参数:例如executor是不是函数啊

  2. 初始化:[[State]]=pending[[FulfillReactions]]=[],[[RejectReactions]]=[]

  3. 创建resolve对象:{[[Resolve]]: resolve, [[Reject]]: reject}

  4. 执行executor:executor(resolve, reject)

因此构造函数里面传入的excuter是立即被执行的。FulfillReactions存储着promise执行成功时要做的操作,RejectReactions存储着promise是要执行的操作。

function Promise(resolver) {  this._id = counter++;  this._state = PENDING;  this._result = undefined;  this._subscribers = [];  var promise = this;  if (noop !== resolver) {    try {      resolver(function (value) {        resolve(promise, value);      }, function (reason) {        reject(promise, reason);      });    } catch (e) {      reject(promise, e);    }  }}

FulfillPromise(promise, value)

  1. 检查[[state]],必须为pending(不是pending的表示已经解析,不能重复解析)

  2. 赋值:[[Result]]=value[[state]]=fulfilled

  3. 触发[[FulfillReactions]]的操作

和FulfillPromise联系最紧密的就是ResolvePromise了,这里我们给出的是ResolvePromise的实现,区别只是多了直接解析Promise。

function resolve(promise, value) {  // 要resolve的为promise(then的callback返回的是promise)  if (typeof value === 'object'    && promise.constructor === value.constructor) {    handleOwnThenable(promise, value);  }   // 要resolve的是值  else {    if (promise._state !== PENDING) { return; }    promise._result = value;    promise._state = FULFILLED;    asap(publish, promise);  }}function handleOwnThenable(promise, thenable) {  // 如果返回的promise已经完成  // 直接用该promise的值resolve父promise  if (thenable._state === FULFILLED) {    resolve(promise, thenable._result);  } else if (thenable._state === REJECTED) {    reject(promise, thenable._result);  }  // 如果返回的promise未完成  // 要等该promise完成再resolve父promise  else {    subscribe(thenable, undefined, function(value) {      resolve(promise, value);    }, function(reason) {      reject(promise, reason);    });  }}

RejectPromise(promise, reason)

  1. 检查[[state]],必须为pending(不是pending的表示已经解析,不能重复解析)

  2. 赋值:[[Result]]=reason[[state]]=rejected

  3. 触发[[RejectReactions]]的操作

触发[[FulfillReactions]]和触发[[RejectReactions]]实际就是遍历数组,执行所有的回调函数。

function reject(promise, reason) {  if (promise._state !== PENDING) { return; }  promise._state = REJECTED;  promise._result = reason;  asap(publish, promise);}

Promise.prototype.then(onFullfilled, onRejected)

  1. promise=this

  2. 新建resultCapability三元组,{[[Promise]], [[Resolve]], [[Reject]]}([[Promise]]新建的)

  3. fulfillReaction={[[Capabilities]]: resultCapability, [[Handler]]: onFulfilled}

  4. rejectReaction={[[Capabilities]]: resultCapability, [[Handler]]: onRejected}

  5. 如果[[state]]是pending:fulfillReaction加入[[FulfillReactions]],rejectReaction加入[[RejectReactions]]

  6. 如果[[state]]是fulfilled:fulfillReaction加入执行队列

  7. 如果[[state]]是rejected:rejectReaction加入执行队列

  8. 返回resultCapability.[[Promise]]

这里可以看出构造函数和then的关系是很紧密的,新建的promise如果是异步操作,那么状态就是pending,调用then时会新建子promise,并且将回调操作加入父promise的[[FulfillReactions]]或[[RejectReactions]]的数组里,这实际就是发布订阅模式

他们是这样的关系:

promise构造函数与then的关系

无论是new promise还是调用then或catch,都会得到一个新的promise,这些promise都会订阅父级promise的完成事件,父级promise完成之后就会执行一系列的回调操作,也就是发布。

Promise.prototype.catch(onRejected)

  • then的语法糖:then(null, onRejected)

下面就是Promise原型:

Promise.prototype = {  constructor: Promise,  then: function (onFulfillment, onRejection) {    var parent = this;    var state = parent._state;    if (state === FULFILLED && !onFulfillment        || state === REJECTED && !onRejection) {        return this;    }    var child = new Promise(noop);    var result = parent._result;    if (state) {      var callback = arguments[state - 1];      asap(function () {        invokeCallback(state, child, callback, result);      });    } else {      subscribe(parent, child, onFulfillment, onRejection);    }    return child;  },  'catch': function (onRejection) {    return this.then(null, onRejection);  }};

Promise.resolve(value)

  1. 新建promise

  2. 调用ResolvePromise(promise, value)(未列出,会判断一些情况然后调用FulfillPromise)

  3. 返回promise

Promise.resolve = function (arg) {  var child = new Promise(noop);  resolve(child, arg);  return child;};

Promise.reject(value)

  1. 新建promise

  2. 调用RejectPromise(promise, value)

  3. 返回promise

Promise.reject = function (reason) {  var child = new Promise(noop);  reject(child, reason);  return child;};

Promise.all(iterator)

到这里我们已经能够实现基本的promise了,Promise.allPromise.race就不继续描述了,有兴趣的可以继续去读规范,这里上图来说明我对这两个函数的理解:

promise.all

调用promise.all会新建一个对象来存储所有promise的处理状态,保存执行的结果,当remain为0时,就可以resolve 新建的promise,这样就可以继续往后执行了。

Promise.all = function (promises) {  var child = new Promise(noop);  var record = {    remain: promises.length,    values: []  };  promises.forEach(function (promise, i) {    if (promise._state === PENDING) {      subscribe(promise, undefined, onFulfilled(i), onRejected);    } else if (promise._state === REJECTED) {      reject(child, promise._result);      return false;    } else {      --record.remain;      record.values[i] = promise._result;      if (record.remain == 0) {        resolve(child, values);      }   }  });  return child;  function onFulfilled(i) {    return function (val) {      --record.remain;      record.values[i] = val;      if (record.remian === 0) {        resolve(child, record.values);      }    }  }    function onRejected(reason) {    reject(child, reason);  }};

Promise.race(iterator)

promise.race与promise.all类似,不过只要有一个promise完成了,我们就可以resolve新建的promise了。

Promise.race = function (promises) {  var child = new Promise(noop);  promises.forEach(function (promise, i) {    if (promise._state === PENDING) {      subscribe(promise, undefined, onFulfilled, onRejected);    } else if (promise._state === REJECTED) {      reject(child, promise._result);      return false;    } else {      resolve(child, promise._result);      return false;    }  });  return child;  function onFulfilled(val) {    resolve(child, val);  }          function onRejected(reason) {    reject(child, reason);  }};

这就是promise的基本内容了,完整代码请戳。

其他问题

promises 穿透

如果传入then里面的参数不是函数,就会被忽略,这就是promise穿透的原因,所以永远往then里面传递函数。答案可以从then方法里面调用的一个关键函数invokeCallback中找到答案:

function invokeCallback(settled, promise, callback, detail) {    var hasCallback = (typeof callback === 'function'),      value, error, succeeded, failed;    if (hasCallback) {      try {        value = callback(detail);      } catch (e) {        value = {          error: e        };      }      if (value && !!value.error) {        failed = true;        error = value.error;        value = null;      } else {        succeeded = true;      }    }    // then的参数不是函数    // 会被忽略,也就是promise穿透    else {      value = detail;      succeeded = true;    }    if (promise._state === PENDING) {      if (hasCallback && succeeded          || settled === FULFILLED) {        resolve(promise, value);      } else if (failed || settled === REJECTED) {        reject(promise, error);      }    }  }

例如如下例子,结果都是输出foo:

Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {  console.log(result);});Promise.resolve('foo').then(null).then(function (result) {  console.log(result);});

拥抱金字塔

promise能够很好的解决金字塔问题,但是有时候我们也是需要适当使用金字塔的,例如我们要同时获取两个promise的结果,但是这两个promise是有关联的,也就是有顺序的,该怎么办?

也许解决方案会是这样,定义一个全局变量,这样在第二个then里面就可以使用两个promise的结果了。

var user;getUserByName('nolan').then(function (result) {  user = result;  return getUserAccountById(user.id);}).then(function (userAccount) {  // 好了, "user" 和 "userAccount" 都有了});

但是这不是最好的方案,此时何不抛弃成见,拥抱金字塔:

getUserByName('nolan').then(function (user) {  return getUserAccountById(user.id).then(function (userAccount) {    // 好了, "user" 和 "userAccount" 都有了  });});

promise是如此强大而且难以理解,但是抓住实质之后其实并没有想象的那么复杂,这也是为什么我要写下这篇文章。更过关于如何正确使用promise,请看第三篇,强力推荐。

参考

转载地址:http://ihmjl.baihongyu.com/

你可能感兴趣的文章
华为7.0系统手机最完美激活Xposed框架的教程
查看>>
繁忙的IT基础设施可能导致安全灾难
查看>>
Objective-C之Block
查看>>
iOS 图片加载框架-SDWebImage 解读
查看>>
Flash ActionScript3.0 中txt文档根据模板转换成html文档的实现
查看>>
安景业安京业安敬业anjingye
查看>>
java rest的说明
查看>>
Angular在页面加载很慢的时候,会出现双花括号的问题
查看>>
JS判断客户端是否是iOS或者Android手机移动端
查看>>
我的友情链接
查看>>
清除浏览器自动填充用户名、密码框
查看>>
HTML5 meta viewport参数详解
查看>>
C#中==、Equals、ReferenceEquals的区别是什么
查看>>
echo的特殊用法-字符颜色控制
查看>>
linux系统inittab文件丢失故障
查看>>
clean code
查看>>
程序员与HR博弈之:有城府的表达你的兴趣爱好
查看>>
vm虚拟机导入时候找不到ip
查看>>
Apache配置——通过rewrite限制某个目录
查看>>
简单记事本和简单四则运算计算器源代码(*.h *.cpp main.cpp)
查看>>