Promise源码解析

Promise源码解析

纸上得来终觉浅,绝知此事要躬行。之前只是很浅显的知道Promise的用法,也大概猜测到它的内部是如何实现的。但是总是有一种不深究一下就不踏实的感觉。于是从npm上获得早期的Promise源代码,拿过来读一读,做一做笔记。

Promise的源码写的非常的巧妙,凭空阅读会陷入入其中无法自拔。

简单的Promise用法

一个简单的Promise用法如下:

const promiseObj = new Promise(function (resolve, reject) {// 代码执行resolve('value');//orreject('error');
});
promiseObj.then(function (value) {// do sth...
}, function (error) {// deal excption...
});
promiseObj.catch(function (error) { // catch excption...
});

我们就从这个简单的例子开始分析Promise的执行是怎么样的。

Promise的源代码位于:https://github.com/stefanpenner/es6-promise.git ,这里采用的版本为0.1.0版本。

我们从Promise的构造函数开始说起:

function Promise(resolver) {... // 省略的部分为必要的校验this._subscribers = [];invokeResolver(resolver, this);
}

_subscribers对象用于存储这个Promise实例所对应的观察者。紧接着执行invokeResolver();

function invokeResolver(resolver, promise) {function resolvePromise(value) {resolve(promise, value); // p2, p1}function rejectPromise(reason) {reject(promise, reason);}try {resolver(resolvePromise, rejectPromise);} catch(e) {rejectPromise(e);}
}

invokeResolver为关键的一环。在这里,构造Promise对象时所传入的方法会被执行,并将执行方法所需的两个回调参数resolvePromise和rejectPromise传了进去,方便业务代码通知Promise对象执行结果。

如果我们的代码很简单的执行了一行代码,例如:

  resolve('Hello');

那么resolvePromise会紧接着调用resolve方法。我们进入resolve方法一探究竟:

function resolve(promise, value) { // promise为新构造的Promise对象,value = 'Hello'.if (promise === value) {fulfill(promise, value);} else if (!handleThenable(promise, value)) {fulfill(promise, value);}
}

我们现在的逻辑会直接进入fulfill方法:

function fulfill(promise, value) {// promise为新构造的Promise对象,value = 'Hello'.if (promise._state !== PENDING) { return; } // 当前不满足,默认为PENDING状态promise._state = SEALED;// promise._state = SEALED promise._detail = value;// promise._detail = 'Hello' config.async(publishFulfillment, promise);
}

到这里,将结果值赋值给了Promise对象。也就是说Promise对象保留了计算后的结果值’Hello’。然后我们看一下config.async()是个什么鬼:

function asap(callback, arg) {var length = queue.push([callback, arg]);if (length === 1) {// If length is 1, that means that we need to schedule an async flush.// If additional callbacks are queued before the queue is flushed, they// will be processed by this flush that we are scheduling.scheduleFlush();}
}

上面这段代码位于lib/promise/asap.js中。它主要用来将任务callback加入下一个时间片中。执行到这里会将[publishFulfillment, promise]组成一个数组放在队列中,然后紧接着根据平台进行任务刷新,也就是执行scheduleFlush();

不过,不管scheduleFlush方法再怎么快,它也是被放在了所有事件最后才执行。所以接下来执行的代码是主线程继续执行的代码:

promiseObj.then(function (value) {// do sth...
}, function (error) {// deal excption...
});
promiseObj.catch(function (error) { // catch excption...
});

到这里我们需要看看Promise.then方法是怎么执行的:

  then: function(onFulfillment, onRejection) {var promise = this;var thenPromise = new this.constructor(function() {});if (this._state) {var callbacks = arguments;config.async(function invokePromiseCallback() {invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);});} else {subscribe(this, thenPromise, onFulfillment, onRejection);}return thenPromise;},

在调用then方法时,产生了一个新的Promise对象thenPromise,它会参与后面的任务执行。现在我们根据上下文进入subscribe方法:

function subscribe(parent, child, onFulfillment, onRejection) { // parent = customPromiseObject, child = thenPromise, onFulfillment = 成功回调,onRejection = 失败回调var subscribers = parent._subscribers;var length = subscribers.length;subscribers[length] = child;subscribers[length + FULFILLED] = onFulfillment;subscribers[length + REJECTED]  = onRejection;
}

subscribers初始化是一个空数组,所以在这里执行完毕后,将会是以下效果:

  subscribers[0] = thenPromise;subscribers[1] = 成功回调;  subscribers[2] = 失败回调;promise._subscribers = subscribers;

由于我们的示例代码使用了catch,所以贴一下catch方法的源代码:

  'catch': function(onRejection) {return this.then(null, onRejection);}

所以在执行完then方法以及catch方法之后,promise._ subscribers内部如下:

  subscribers[0] = thenPromise;subscribers[1] = 成功回调;  subscribers[2] = 失败回调;  subscribers[3] = thenPromise2;subscribers[4] = null;  subscribers[5] = catch回调;

到这里,我们的同步事件就执行完了。接下来开始分析异步事件。我们回到lib/promise/asap.js文件。

在Promise早期的源代码中,对Promise的运行平台做了区分。我们这里对这块细节不做深究,但不管是哪个平台,最终还是会执行到flush方法。这里需要注意:在执行flush方法时,它已经在所有事件执行之后了。这里的所有事件指的是已经进入主线程队列的事件,也就是刚刚执行完成的同步事件。

function flush() {for (var i = 0; i < queue.length; i++) {var tuple = queue[i];var callback = tuple[0], arg = tuple[1];callback(arg);}queue = [];
}

flush方法在这里会回调刚刚传入的方法publishFulfillment,参数为那个promise对象。我们确认一下publishFulfillment方法的细节:

function publishFulfillment(promise) {publish(promise, promise._state = FULFILLED);
}

它这里很简单,直接调用了publish方法。不过在这里,promise对象的状态再一次被改变了。从PENDING -> SEALED -> FULFILLED。我们进入publish:

function publish(promise, settled) { // promise = new Promise, settled = FULFILLEDvar child, callback, subscribers = promise._subscribers, detail = promise._detail;for (var i = 0; i < subscribers.length; i += 3) { child = subscribers[i];callback = subscribers[i + settled]; invokeCallback(settled, child, callback, detail); }promise._subscribers = null;
}

上面的代码开始Promise对象的观察者进行通知。注意这里的for循环只循环了两次。第一次循环:

  child == thenPromise;callback == 成功回调;

我们先来看正常的执行,这里直接调用了invokeCallback方法:

function invokeCallback(settled, promise, callback, detail) { settled = FULFILLED, promise = thenPromise, callback = 成功回调, detail = 'Hello'// 检测callback是否是一个方法var hasCallback = isFunction(callback),value, error, succeeded, failed;// 如果是方法,则执行if (hasCallback) {try {value = callback(detail);  succeeded = true;} catch(e) {failed = true;error = e;}} else {value = detail;succeeded = true;}if (handleThenable(promise, value)) {return;} else if (hasCallback && succeeded) {resolve(promise, value);} else if (failed) {reject(promise, error);} else if (settled === FULFILLED) {resolve(promise, value);} else if (settled === REJECTED) {reject(promise, value);}
}

咱们的示例比较简单,在检测回调方法是一个方法之后,就直接调用了。我们的示例也没有返回值。所以直接走进了hasCallback && succeeded的判断中,然后进入resolve方法。

function resolve(promise, value) { // promise = thenPromise, value = undefinedif (promise === value) {fulfill(promise, value);} else if (!handleThenable(promise, value)) {fulfill(promise, value);}
}

根据上下文,这里进入了fulfill方法:

function fulfill(promise, value) {// promise = thenPromise, value = null if (promise._state !== PENDING) { return; }promise._state = SEALED;// thenPromise._state = SEALED promise._detail = value;// thenPromise._detail = null config.async(publishFulfillment, promise);
}

到这里是不是似曾相识?没错,我们在上面已经见过上面两个方法。不过在这里是要通过flush回调执行thenPromise对象。然后根据上面提到的逻辑,最后thenPromise将会进入publish:

function publish(promise, settled) { // promise = thenPromise, settled = FULFILLED var child, callback, subscribers = promise._subscribers, detail = promise._detail;for (var i = 0; i < subscribers.length; i += 3) { child = subscribers[i];callback = subscribers[i + settled]; invokeCallback(settled, child, callback, detail); }promise._subscribers = null;
}

不过thenPromise对象是一个空对象,它的_subscribers属性是一个空数组。所以这里自然执行完毕。

不过到这里也才是thenPromise执行完毕,我们自己构造的Promise对象呢?它才准备进行第二次for循环:

  child == thenPromise2;callback == null;

然后根据上下文,在执行到invokeCallback方法,不过最终它的命运匹配到了settled === FULFILLED的条件,进入了resolve方法:

function resolve(promise, value) { // promise = new Promise, value = undefinedif (promise === value) {fulfill(promise, value);} else if (!handleThenable(promise, value)) {fulfill(promise, value);}
}

再进入fulfill方法:

function fulfill(promise, value) {if (promise._state !== PENDING) { return; }promise._state = SEALED;promise._detail = value;config.async(publishFulfillment, promise);
}

不过执行到这里,promise的_state已经为SEALED了,不等于PENDING,所以Promise也就自然终结了。

至此,一个普通的Promise执行终结。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/478672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

参会邀请 - CCKS2020 | 2020全国知识图谱与语义计算大会(CCKS2020)明日开幕

本文转载自公众号&#xff1a; 中国中文信息学会。第十四届全国知识图谱与语义计算大会将于2020年11月12日-15日在南昌召开。会议由中国中文信息学会语言与知识计算专业委员会主办&#xff0c;由江西师范大学承办&#xff0c;智源社区提供社区支持。本次会议讲习班采用线上举行…

Linux 程 序 员 失 业 警 告

文 | 小戏有多少人期待过像贾维斯一样的强人工智能&#xff1f;尽管老实说看当下的技术离这一期待还很遥远&#xff0c;但用用类似 GPT-3 这样的技术去实现些朴素的愿望似乎并没有那么困难。就在昨天&#xff0c;来自 Facebook 的 Elvis 在推特上发布了一个借助 OpenAI 的 GPT-…

LeetCode 707. 设计链表(List)

文章目录1. 设计一个单链表2. 双向链表1. 设计一个单链表 在链表类中实现这些功能&#xff1a; get(index)&#xff1a;获取链表中第 index 个节点的值。如果索引无效&#xff0c;则返回-1。 addAtHead(val)&#xff1a;在链表的第一个元素之前添加一个值为 val 的节点。插入…

用Vue.js开发微信小程序:开源框架mpvue解析

前言 mpvue 是一款使用 Vue.js 开发微信小程序的前端框架。使用此框架&#xff0c;开发者将得到完整的 Vue.js 开发体验&#xff0c;同时为 H5 和小程序提供了代码复用的能力。如果想将 H5 项目改造为小程序&#xff0c;或开发小程序后希望将其转换为 H5&#xff0c;mpvue 将是…

axios网络请求框架源码解析

早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持。并且做了对请求参数拼接、Json对象序列化等基本功能。 到0.19.0版本时&#xff0c;内部请求已经变为了在Node环境下与主流浏览器的支持&#xff0c;其中Node环境下支持http请求与https请求。并且支持取消…

修改安装路径 pip 以及修改运行路径

持久化安装 如果需要进行持久化安装, 需要使用持久化路径, 如下方代码示例: !mkdir /home/aistudio/external-libraries !pip install beautifulsoup4 -t /home/aistudio/external-libraries同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: import s…

对比学习有多火?文本聚类都被刷爆了…

文 | 花小花Posy大家好&#xff0c;我是小花。对比学习的大火???? 越来越旺了&#xff0c;已然从CV蔓延到NLP了。今天给大家介绍的正是一篇将对比学习应用到文本聚类上的工作&#xff0c;NAACL21新鲜出炉的paper——《Supporting Clustering with Contrastive Learning》。…

论文浅尝 - WWW2020 | 生成多跳推理问题以改善机器阅读理解能力

论文笔记整理&#xff1a;谭亦鸣&#xff0c;东南大学博士生。来源&#xff1a;WWW 2020链接&#xff1a;https://dl.acm.org/doi/pdf/10.1145/3366423.3380114概述这篇论文关注的任务是&#xff1a;基于给定文本的“多跳问题生成”&#xff08;多关系问题&#xff09;。作者提…

记一次Vue框架升级

框架升级背景 公司目前业务迭代很快&#xff0c;且大部分的流量都在公众号上。然而我们公众号所使用的框架却是3年前的Vue 1.0.16。面对Vue这3年来带来的无数新特性&#xff0c;我们只能望洋兴叹&#xff1a;看得见&#xff0c;摸不着&#xff0c;因为升级这事看起来太难了。 …

谈谈NLP下一个主战场:万亿参数的预训练模型!

自从BERT诞生以来&#xff0c;各大互联网巨头之间就展开了预训练语言模型军备竞赛&#xff0c;XLNet、ERNIE、RoBERTa、T5、GPT-3....但当事情进展到号称自己是zero-shot learner的GPT-3时&#xff0c;批判的声音变得明显多了。这么大&#xff0c;能用吗&#xff1f;真的能做到…

交互式调试器

import pdb pdb.set_trace()

人物志 | 美团女技术总监任登君:不要给自己的人生设限

在我们美团技术团队超过6000名工程师中&#xff0c;有众多的女同学&#xff0c;她们是支撑中国领先的生活服务电子商务平台不可或缺的力量。3月8日女神节&#xff0c;我们专访了她们的代表——美团广告平台技术负责人任登君。登君也是我们团队里目前职位最高的女性技术Leader&a…

论文浅尝 - ISWC2020 | KnowlyBERT: 知识图谱结合语言模型补全图谱查询

论文笔记整理&#xff1a;胡楠&#xff0c;东南大学博士。来源&#xff1a;ISWC 2020动机像Wikidata这样的现代知识图已经捕获了数十亿个RDF三元组&#xff0c;但是它们仍然缺乏对大多数关系的良好覆盖。同时在NLP研究的最新进展表明&#xff0c;可以轻松地查询神经语言模型以获…

Webpack构建性能优化指南

本指南翻译自webpack官方性能指南文档&#xff1a;https://webpack.js.org/guides/build-performance/ 构建性能 本指南涵盖了对增进构建或编译性能的一些有效的提示。 General 以下提示对开发环境或者生产环境都有效。 Stay Up to Date 保持最新的webpack版本。我们总是在…

LeetCode 92. 反转链表 II(双指针)

1. 题目 反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。 说明: 1 ≤ m ≤ n ≤ 链表长度。 示例:输入: 1->2->3->4->5->NULL, m 2, n 4 输出: 1->4->3->2->5->NULL来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xf…

我对你的爱,是只为你而留的神经元

文 | 白鹡鸰有一个小轶专属神经元编 | 小轶有一个白鹡鸰专属神经元什么是苹果&#xff1f;红的&#xff1f;绿的&#xff1f;黄的&#xff1f;球状&#xff1f;斑点&#xff1f;香气&#xff1f;需要咬上一口才能确定&#xff1f;或者……其实我们在说某家技术公司&#xff1f;…

Android动态日志系统Holmes

背景 美团是全球领先的一站式生活服务平台&#xff0c;为6亿多消费者和超过450万优质商户提供连接线上线下的电子商务网络。美团的业务覆盖了超过200个丰富品类和2800个城区县网络&#xff0c;在餐饮、外卖、酒店旅游、丽人、家庭、休闲娱乐等领域具有领先的市场地位。平台大&a…

领域应用 | 知识图谱在小米的应用与探索

本文转载自公众号&#xff1a;DataFunTalk。分享嘉宾&#xff1a;彭力 小米编辑整理&#xff1a;马瑶出品平台&#xff1a;DataFunTalk导读&#xff1a;小米知识图谱于2017年创立&#xff0c;已支持公司了每天亿级的访问&#xff0c;已赋能小爱同学&#xff0c;小米有品、智能问…

前端应用开发架构图谱

个人整理的前端架构图谱&#xff0c;之后会根据这个图谱不断的完善内容。希望这个图谱可以对开发同学的知识脉络有个梳理的作用。 相关图谱文件已上传至Github&#xff1a;https://github.com/sahadev/front-end-architecture&#xff0c;后续将不定期更新。 2020年02月28日已…

丹琦女神新作:对比学习,简单到只需要Dropout两下

文 | 花小花Posy上周把 《对比学习有多火&#xff1f;文本聚类都被刷爆了...》分享到卖萌屋的群里后&#xff0c;遭到了群友们一波嫌弃安利。小伙伴们表示&#xff0c;插入替换的数据增强方式已经Out了&#xff0c;SimCSE才是现在的靓仔。snowfloating说&#xff1a;看完Danqi …