作者: HerryLo

本文永久有效链接: https://github.com/AttemptWeb......

Promises对象被用于表示一个异步操作的最终完成 (或失败), 及其结果值。主要是为了解决异步操作的问题。

#Promise对象的状态

一个 Promise对象有以下三种状态:

pending: 初始状态,既不是成功,也不是失败状态。
fulfilled(resolved): 意味着操作成功完成。
rejected: 意味着操作失败。

Promise对象内部运行的一个变化, 变化如下:

1. 当new Promise()被实例化后,即表示Promise 进入pending初始化状态,准备就绪,等待运行。
2. 一旦Promise实例运行成功或者失败之后,实例状态就会变为fulfilled 或者 rejected,此时状态就无法变更。

#Promise函数使用

任何系统或函数都可以简化为输入输出系统,数据输入 ——> 黑箱 ——> 输出,如下图:

我们可以拿上图来类比Promise函数,代码如下:

// 实例化 Promise
new Promise((resolve, reject)=> {
// 输入
AjaxRequest.post({
url: 'url',
data: {},
sueccess: ()=> {
// resolve
resolve(res)
},
fail: (err)=> {
// reject
reject(err)
}
})
}).then((res)=> {
// res 输出
// ...操作
}).catch((err)=> {
// err 输出
// ...操作
})

在上面的代码中,Promise函数参数可以作为输入信息,而后经过Promise的内部处理(黑箱),在then函数或者catch函数参数中输出信息,这是一个完整的系统(别被它分散了注意力,这个解释的目的:让你更加关注Promise函数内部实现)。下面我们将解析Promise中黑箱操作。

#pending状态下会运行的函数

Promise函数实例化,会先进入到pending状态,在这个状态下,它会运行如下函数:

  1. 实例化Promise构造函数

  2. then方法注册回调函数

  3. catch方法注册回调函数

  4. 调用doResolve函数执行fn

#实例化Promise构造函数

你可以直接查看源码:Promise函数:54行,对照阅读,同时,在下面的代码中我会做不必要的省略。

// 首先运行,Promise构造函数
function Promise(fn) {
// ...省略检验 // _deferreds的类型,1是 single,2是 array
this._deferredState = 0;
// 0 - pending
// 1 - fulfilled(resolved)
// 2 - rejected
// 3 - 另一个Promise的状态
this._state = 0;
// promise 执行结果
this._value = null;
// then注册回调数组
this._deferreds = null;
// fn等于noop 即return
if (fn === noop) return;
// 接受Promise回调函数 和 this 作为参数
doResolve(fn, this);
}

Promise构造函数,会初始化属性,其中参数fn就是我们传入的函数。其中doResolve函数接受Promise函数参数 和 this作为参数,this指向它自己,负责执行fn函数。等下面的then函数和catch函数的回调函数注册完之后,doResolve函数将立即执行。

#then方法注册回调函数

可以查看代码,查看源码:then函数:72行。then方法的回调函数会被存储在this._deferreds中。仔细阅读代码中的备注

Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor !== Promise) {
// safeThen函数也是通过调用handle函数,return 新的Promise对象
return safeThen(this, onFulfilled, onRejected);
}
// 生成新的Promise对象
var res = new Promise(noop);
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
}; // Handler构造函数
// 它的作用是挂载 then中的回调函数 和 一个空的Promise对象
function Handler(onFulfilled, onRejected, promise){
// then中的Fulfilled回调函数
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
// then中的Rejected回调函数
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
// 保存新的Promise
this.promise = promise;
}
// 保存then注册回调函数,更新回调函数状态
function handle(self, deferred) {
// 。。。省略 // pedding 状态
if (self._state === 0) {
// deferred == new Handler(onFulfilled, onRejected, res)
if (self._deferredState === 0) {
self._deferredState = 1;
// 存储then回调deferred对象
self._deferreds = deferred;
return;
}
if (self._deferredState === 1) {
self._deferredState = 2;
// 存储then回调deferred对象
self._deferreds = [self._deferreds, deferred];
return;
}
// 存储then回调函数对象
self._deferreds.push(deferred);
return;
} // 只有当进入到非pedding状态,handleResolved才会运行
handleResolved(self, deferred);
}

Handler函数生成一个deffer对象,用于保存then函数中的onFulfilled和onRejected回调,以及返回的新的promise实例

then方法中的核心函数就是handle函数,它负责接收thisnew Handler对象。若在pedding状态下,handle函数只负责注册回调函数,更新回调函数状态。在非pedding状态下,则会执行handleResolved函数。

#catch方法注册回调函数

查看源码:catch函数:105行

Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};

catch方法的回调函数实际是通过then方法来完成保存的。

#调用doResolve函数执行fn

负责运行Promise实例对象中的回调函数参数fn。

// 调用doResolve函数
function doResolve(fn, promise) {
var done = false; // tryCallTwo函数执行 类似于
// (resolve, reject) => {if(err){reject(err);return};resolve(res)}执行;
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
reject(promise, reason);
}); // fn函数调用失败,手动运行reject函数
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}

doResolve是同步直接调用传入的函数。其中tryCallTwo函数作用是调用函数fn,它接受三个参数。先执行fn函数,根据结果,再执行resolve函数或reject函数。在resolve函数或reject函数被调用之前,Promise对象的状态依然是pending

pending状态下函数调用基本流程如下:

#进入resolve或reject状态时会运行的函数

当初始化完之后,fn函数执行完成,接下来就会运行resolve函数或者reject函数。

  1. 调用resolve函数

  2. 调用finale函数

  3. 调用handleResolved函数

#调用resolve函数

若Promise对象的fn函数执行正常,之后就会调用resolve函数。可以查看源码:resolve函数:131行

function resolve(self, newValue) {
// 。。。省略 // newValue存在 & (newValue是一个对象 || newValue是一个函数)
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
// 获取then函数
var then = getThen(newValue);
// 。。。省略 if (
then === self.then &&
newValue instanceof Promise
) {
// 如果newValue 是一个Promise对象,那么调用finale函数
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
// 如果newValue 是一个函数,就继续调用doResolve函数
doResolve(then.bind(newValue), self);
return;
}
}
// 标记完成,进入结束流程
self._state = 1;
self._value = newValue;
finale(self);
}

确认newValue的值,如果newValue是一个函数,就继续循环调用doResolve函数;如果newValue 是一个Promise对象,那么就直接调用finale函数。都不是,则直接调用finale函数。

#调用finale函数

进入结束流程,finale结束。

function finale(self) {
// 单个回调
if (self._deferredState === 1) {
// 执行handle函数,实际是执行handleResolved
handle(self, self._deferreds);
self._deferreds = null;
}
// 回调数组
if (self._deferredState === 2) {
for (var i = 0; i < self._deferreds.length; i++) {
// 执行handle函数,实际是执行handleResolved
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
}

finale函数表示进入结束流程,执行handle函数。同时在上面已经说到,在非pedding状态下,执行handle函数,实际会是执行handleResolved函数。

#调用handleResolved函数

handleResolved负责收尾工作,负责执行then或者catch方法注册的回调函数。仔细阅读代码中的备注

var asap = require('asap/raw');

function handleResolved(self, deferred) {
asap(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
// 不存在 onFulfilled & onRejected
// deferred.promise 只是一个空的Promise对象
if (cb === null) {
// 1 - fulfilled(resolved)
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
// 执行cb回调函数
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
// 错误,报reject
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
});
}

通过异步asap调用,若不存在onFulfilledonRejected,直接调用resolvereject。若存在,则tryCallOne回调的结果,直接调用resolvereject。其中的deferred就是上文提到的new Handler实例对象。真正会影响最后这步流程的,其实是deferred.onFulfilled或者 deferred.onRejected的回调执行,执行完回调后,这个Promise的执行过程就基本完成。

reject函数在这里我就不说了,有兴趣的可以看查看源码:reject函数

Promise对象调用函数的基本流程图,只是一个大致的走向,便于理解:

#参考

Promises/A+ 规范

MDN中文: Promise对象

Github: then/promise 源码

tc39: tc39 ecma262 promise

#感谢

掘金:代码君的自由:解读Promise内部实现原理

简书:乌龟怕铁锤:Promise 源代码解析

ps: 微信公众号:Yopai,有兴趣的可以关注,每周不定期更新,分享可以增加世界的快乐

Promise核心原理解析的更多相关文章

  1. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  2. Java并发包JUC核心原理解析

    CS-LogN思维导图:记录CS基础 面试题 开源地址:https://github.com/FISHers6/CS-LogN JUC 分类 线程管理 线程池相关类 Executor.Executor ...

  3. 「进阶篇」Vue Router 核心原理解析

    前言 此篇为进阶篇,希望读者有 Vue.js,Vue Router 的使用经验,并对 Vue.js 核心原理有简单了解: 不会大篇幅手撕源码,会贴最核心的源码,对应的官方仓库源码地址会放到超上,可以配 ...

  4. ibatis 核心原理解析!

    关注下方公众号,可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料. 最近查找一个生产问题的原因,需要深入研究 ibatis 框架的源码.虽然最后证明问题的原因与 ibat ...

  5. ibatis 核心原理解析

    最近查找一个生产问题的原因,需要深入研究 ibatis 框架的源码.虽然最后证明问题的原因与 ibatis 无关,但是这个过程加深了对 ibatis 框架原理的理解. 这篇文章主要就来讲讲 ibati ...

  6. NameServer 核心原理解析

    在之前的文章中,已经把 Broker.Producer 和 Conusmer 的部分源码和核心的机制介绍的差不多了,但是其实 RocketMQ 中还有一个比较关键但是我们平时很容易忽略的组件--Nam ...

  7. 【转】GeoHash核心原理解析

    好久没更新过博客了,先转载一篇文章吧. 源地址:http://www.cnblogs.com/LBSer/p/3310455.html 引子 机机是个好动又好学的孩子,平日里就喜欢拿着手机地图点点按按 ...

  8. GeoHash核心原理解析

    http://www.cnblogs.com/LBSer/p/3310455.html 引子 机机是个好动又好学的孩子,平日里就喜欢拿着手机地图点点按按来查询一些好玩的东西.某一天机机到北海公园游玩, ...

  9. [转]GeoHash核心原理解析

    原文出处: zhanlijun    引子 机机是个好动又好学的孩子,平日里就喜欢拿着手机地图点点按按来查询一些好玩的东西.某一天机机到北海公园游玩,肚肚饿了,于是乎打开手机地图,搜索北海公园附近的餐 ...

随机推荐

  1. git之旅,畅游git的世界

    今天小铭酱带大家探索一下git的奥秘! 1.初步探索 首先我在一个空的文件夹新建了一个名叫hello.html文件,文件内容只有一句话“hello git”.我们先引入git,看看git能为我们它能干 ...

  2. bilibili弹幕爬取与比对分析

    最近受人之托研究了下b站的数据爬取做个小工具,最后朋友说不需要了,本着开源共享的原则,将研究成果与大家分享一波,话不多说直接上干货 需求分析 给定up主uid和用户uid,爬取用户在该up主所有视频中 ...

  3. 【在 Nervos CKB 上做开发】Nervos CKB脚本编程简介[2]:脚本基础

    CKB脚本编程简介[2]:脚本基础 原文作者:Xuejie 原文链接:Introduction to CKB Script Programming 2: Script 本文译者:Shooter,Jas ...

  4. Scala 系列(八)—— 类和对象

    一.初识类和对象 Scala 的类与 Java 的类具有非常多的相似性,示例如下: // 1. 在 scala 中,类不需要用 public 声明,所有的类都具有公共的可见性 class Person ...

  5. Linux CentOS7 下设置tomcat 开机自启动

    网上有很多教程说是可以设置Tomcat 自启动,但是一一验证了都不行.最后找到一个方法 验证可以: 1.改rc.local   位于/etc/rc.d/文件下的rc.local   vi /etc/r ...

  6. css3的@media

    都知道bootstrap响应式布局很酷,但是是怎么实现的呢?其官网首页有提到这一切的功劳都是来自于CSS 媒体查询(Media Query). 使用 @media 查询,你可以针对不同的媒体类型定义不 ...

  7. JavaWeb实现增删查改(图书信息管理)——之查询

     关于此次CRUD所需要的jar包,本人把文件放在了百度网盘,需要的自行去下载: 链接:https://pan.baidu.com/s/1Pqe88u6aPaeVjjOq1YFQ-w  提取码:pim ...

  8. Git 忽略某些文件提交

    在项目中有些配置文件不需要提交,但是有同学在后面开发中发现在.igonore文件中无论如何都无法忽略某些文件的提交.原因在这里: 已经维护起来的文件,即使加上了gitignore,也无济于事.---- ...

  9. CF EDU - E. Lomsat gelral 树上启发式合并

    学习:http://codeforces.com/blog/entry/44351 E. Lomsat gelral 题意: 给定一个以1为根节点的树,每个节点都有一个颜色,问每个节点的子树中,颜色最 ...

  10. codeforces E. Phone Talks(dp)

    题目链接:http://codeforces.com/contest/158/problem/E 题意:给出一些电话,有打进来的时间和持续的时间,如果人在打电话,那么新打进来的电话入队,如果人没有打电 ...