作者: 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和SVN教程

    各种版本控制工具的简单比较 特性 CVS SVN GIT 并发修改 支持 支持 支持 并发提交 不支持 支持 支持 历史轨迹 不支持更名 支持更名 支持更名 分布式 不支持 不支持 支持 SVN SV ...

  2. IIS配置后本地访问正常,但外网无法访问

    很久没有部署IIS网站项目了,都有些手生了,这不今天就遇到了问题.首先确定的是,我的网站配置没有问题,因为内网访问正常.内网访问情况如下: 但是外网访问时确是这样的: 怎么回事儿呢?我就想是不是防火墙 ...

  3. <lable>标签

    最近用各种框架的时候,发现很多平常自己写代码没注意到的标签和用法,在这里记录一下. 其实是很多细节方面需要注意的写法. <label> 定义:为input元素定义标注 label标签不会向 ...

  4. CSS 锚点 :target属性 制作选项卡

    .pic img:first-of-type{display: block;} .pic img:target{display: block;}

  5. MySQL之Xtrabackup的使用

    一.Xtrabackup介绍 XtraBackup(PXB) 工具是 Percona 公司用 perl 语言开发的一个用于 MySQL 数据库物理热备的备份工具,支持 MySQl(Oracle).Pe ...

  6. Leetcode之回溯法专题-77. 组合(Combinations)

    Leetcode之回溯法专题-77. 组合(Combinations)   给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合. 示例: 输入: n = 4, k = 2 输 ...

  7. 第五章 Spring核心概念

     5.1.1 企业级应用开发    企业级应用是指那些为商业组织,大型企业而创建部署的解决方案及应用,大型企业级应用的结构复杂,涉及的外部资源众多,事务密集,数据规模大,用户数量多,有较强的安全性考虑 ...

  8. php中++ --运算

    $x = 5; echo $x; echo "---"; // echo ++$x + $x; //6+6=12 $x=6 // echo ++$x + 5; //6+5=12 $ ...

  9. java基础-多线程二

    java基础-多线程二 继承thread和实现Runnable的多线程每次都需要经历创建和销毁的过程,频繁的创建和销毁大大影响效率,线程池的诞生就可以很好的解决这一个问题,线程池可以充分的利用线程进行 ...

  10. 导航控制器的根控制器 是滚动性&普通的frame区别

    当一个控制器有navigationBar&tabBar: 1.当导航控制器根控制器是tableViewController时,tableView 的frame原点是屏幕左上角,当向tableV ...