您的位置:首页 > 其它

Promise实现原理

2015-07-06 21:40 411 查看

这两天在熟悉 kissy 框架的时候,看到了
Promise
模块。
Promise
对于一个Jser并不陌生,
Promise
类似于一个事务管理器,它的作用就是将各种内嵌回调的事务用流水形式表达。利用
Promise
可以让异步编程更符合人的直觉,让代码逻辑更加清晰,把开发人员从回调地狱中释放出来。这么“高大上”的东西,以前写
nodejs
代码的时候只是简单的用用,还没有理解其基本的实现原理,罪过!个人认为,理解编程思想最好的途径就是阅读一份简易的实现源码。很幸运,网上有不少
Promise
的简易实现,其中 这篇博文 介绍的实现方式非常赞,下面就来好好研究下吧!


基础概念

目前,
Promise
ECMAScript 6
规范的重要特性之一,各大浏览器也开始慢慢支持这一特性。当然,也有一些第三方内库实现了该功能,如: Q when WinJS RSVP.js 等。

Promise
对象用来进行延迟(
deferred
)和异步(
asynchronous
)计算。一个
Promise
处于以下四种状态之一:

pending: 还没有得到肯定或者失败结果,进行中

fulfilled: 成功的操作

rejected: 失败的操作

settled: 已被
fulfilled
rejected


Promise
对象有两个重要的方法,一个是
then
,另一个是
resolve


then:将事务添加到事务队列中

resolve:开启流程,让整个操作从第一个事务开始执行

Promise
常用方式如下:

var p = new Promise(function(resolve, reject) {
...
// 事务触发
resovle(xxx);
...
});
p.then(function(value) {
// 满足
}, function(reason) {
// 拒绝
}).then().then()...

示意图如下:



实现步骤

1.
Promise
其实就是一个状态机。按照它的定义,我们可从如下基础代码开始:

var PENDING = 0;  // 进行中
var FULFILLED = 1; // 成功
var REJECTED = 2;  // 失败

function Promise() {
// 存储PENDING, FULFILLED或者REJECTED的状态
var state = PENDING;

// 存储成功或失败的结果值
var value = null;

// 存储成功或失败的处理程序,通过调用`.then`或者`.done`方法
var handlers= [];

// 成功状态变化
function fulfill(result) {
state = FULFILLED;
value = result;
}
// 失败状态变化
function reject(error) {
state = REJECTED;
value = error;
}
}

2.下面是
Promise
resolve
方法实现:

注意:
resolve
方法可接收的参数有两种:一个普通的值/对象或者一个
Promise
对象。如果是普通的值/对象,则直接把结果传递到下一个对象;如果是一个
Promise
对象,则必须先等待这个子任务序列完成。

function Promise() {
...
function resolve(result) {
try {
var then= getThen(result);
// 如果是一个promise对象
if (then) {
doResolve(then.bind(result), resolve, reject);
return;
}
// 修改状态,传递结果到下一个事务
fulfill(result);
} catch (e) {
reject(e);
}
}
}

两个辅助方法:

/**
* Check if a value is a Promiseand, if it is,
* return the `then` method of that promise.
*
* @param {Promise|Any} value
* @return {Function|Null}
*/
function getThen(value) {
var t = typeof value;
if (value && (t === 'object' || t === 'function')) {
var then= value.then;
if (typeof then=== 'function') {
return then;
}
}
return null;
}

/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilledand onRejectedare only called once.
*
* Makes no guarantees about asynchrony.
*
* @param {Function} fnA resolver function that may not be trusted
* @param {Function} onFulfilled
* @param {Function} onRejected
*/
function doResolve(fn, onFulfilled, onRejected) {
var done= false;
try {
fn(function(value) {
if (done) return;
done= true;
onFulfilled(value);
}, function(reason) {
if (done) return;
done= true;
onRejected(reason);
});
} catch(ex) {
if (done) return;
done= true;
onRejected(ex);
}
}

3.上面已经完成了一个完整的内部状态机,但我们并没有暴露一个方法去解析或则观察
Promise
。现在让我们开始解析
Promise


function Promise(fn) {
...
doResolve(fn, resolve, reject);
}

如你所见,我们复用了
doResolve
,因为对于初始化的
fn
也要对其进行控制。
fn
允许调用
resolve
或则
reject
多次,甚至抛出异常。这完全取决于我们去保证
promise
对象仅被
resolved
或则
rejected
一次,且状态不能随意改变。

4.目前,我们已经有了一个完整的状态机,但我们仍然没有办法去观察它的任何变化。我们最终的目标是实现
then
方法,但
done
方法似乎更简单,所以让我们先实现它。

我们的目标是实现
promise.done(onFullfilled, onRejected)


onFulfilled
onRejected
两者只能有一个被执行,且执行次数为一

该方法仅能被调用一次

一旦调用了该方法,则
promise
链式调用结束

无论是否
promise
已经被解析,都可以调用该方法

var PENDING = 0;  // 进行中
var FULFILLED = 1; // 成功
var REJECTED = 2; // 失败

function Promise() {
// 存储PENDING, FULFILLED或者REJECTED的状态
var state = PENDING;

// 存储成功或失败的结果值
var value = null;

// 存储成功或失败的处理程序,通过调用`.then`或者`.done`方法
var handlers= [];

// 成功状态变化
function fulfill(result) {
state = FULFILLED;
value = result;
handlers.forEach(handle);
handlers= null;
}
// 失败状态变化
function reject(error) {
state = REJECTED;
value = error;
handlers.forEach(handle);
handlers= null;
}

function resolve(result) {
try {
var then= getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result);
} catch (e) {
reject(e);
}
}

// 不同状态,进行不同的处理
function handle(handler) {
if (state === PENDING) {
handlers.push(handler);
} else {
if (state === FULFILLED &&
typeof handler.onFulfilled=== 'function') {
handler.onFulfilled(value);
}
if (state === REJECTED &&
typeof handler.onRejected=== 'function') {
handler.onRejected(value);
}
}
}

this.done= function (onFulfilled, onRejected) {
// 保证异步
setTimeout(function () {
handle({
onFulfilled: onFulfilled,
onRejected: onRejected
});
}, 0);
}

doResolve(fn, resolve, reject);
}

Promise
resolved
或者
rejected
时,我们保证
handlers
将被通知。

5.现在我们已经实现了
done
方法,下面实现
then
方法就很容易了。需要注意的是,我们要在处理程序中新建一个
Promise


this.then= function (onFulfilled, onRejected) {
var self = this;
return new Promise(function (resolve, reject) {
return self.done(function (result) {
if (typeof onFulfilled=== 'function') {
try {
// onFulfilled方法要有返回值!
return resolve(onFulfilled(result));
} catch (ex) {
return reject(ex);
}
} else {
return resolve(result);
}
}, function (error) {
if (typeof onRejected=== 'function') {
try {
return resolve(onRejected(error));
} catch (ex) {
return reject(ex);
}
} else {
return reject(error);
}
});
});
}

测试

完成了上面的代码,测试就很容易啦。偷个懒,测试实例来自MDN:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>promisetest</title>
<script src="http://www.chenjunxyf.me/promiseshi-xian-yuan-li/mypromise.js"></script>
</head>
<body>
<button id="test">promisetest</button>
<div id="log"></div>
<script>
var promiseCount = 0;
function testPromise() {
var thisPromiseCount = ++promiseCount;
var log = document.getElementById('log');
log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 开始(同步代码开始)');

var p1 = new Promise(
function(resolve, reject) {
log.insertAdjacentHTML('beforeend', thisPromiseCount + ') Promise开始(异步代码开始)');

window.setTimeout(function() {
resolve(thisPromiseCount);
}, Math.random() * 2000 + 1000);
}
);

p1.then(
function(val) {
log.insertAdjacentHTML('beforeend', val + ') Promise被满足了(异步代码结束)');
}
);

log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 建立了Promise(同步代码结束)');
}

document.querySelector('button').addEventListener('click', testPromise);
</script>
</body>
</html>

效果:



结语

通过一份简易的实现代码,理解
Promise
原理还是挺容易的。本文所有代码请 戳这 !PS:这次用了 vscode 写代码,感觉非常赞!

参考

JavaScript Promises

Implementing promise

Promise原理解析与实现

细嗅Promise

MDN Promise

How is a promise/defer library implemented?

点赞
前端 javascript
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: