本文大多数内容翻译自该篇文章javascript
Promise能够认为是一种用来解决异步处理的代码规范。常见的异步处理是使用回调函数,回调函数有两种模式,同步的回调和异步的回调。通常回调函数指的是异步的回调。html
同步回调java
function add(a, b, callback) { callback(a + b) }
console.log('before');
add(1, 2, result => console.log('Result: ' + result);
console.log('after');
复制代码
输出结果为: before Result:3 afterweb
异步回调api
function addAsync(a, b, callback) {
setTimeout( () => callback(a + b), 1000);
}
console.log('before');
addAsync(1, 2, result => console.log('Result: ' + result));
console.log('after');
复制代码
输出结果: before after Result: 3promise
然而回调函数有个著名的坑就是“callback hell”,好比:app
doSomething1(function(value1) {
doSomething2(function(value2) {
doSomething3(function(value3) {
console.log("done! The values are: " + [value1, value2, value3].join(','));
})
})
})
复制代码
为了等value1, value2, value3数据都准备好,必需要一层一层嵌套回调函数。若是一直嵌套下去,就造成了callback hell,不利于代码的阅读。异步
若是改用Promise的写法,只要写成以下方式就行。ide
doSomething1().then(function() {
return value1;
}).then(function(tempValue1) {
return [tempValue1, value2].join(',');
}).then(function(tempValue2) {
console.log("done! ", [tempValue2, value3].join(','));
});
复制代码
能够注意到,Promise其实是把回调函数从doSomething
函数中提取到了后面的then
方法里面,从而防止多重嵌套的问题。函数
一个 Promise 对象表明一个目前还不可用,可是在将来的某个时间点能够被解析的值。它要么解析成功,要么失败抛出异常。它容许你以一种同步的方式编写异步代码。
Promise的实现是根据Promises/A+规范实现的。
对于Promise的基本使用和入门,能够参考promise-book。这里对Promise的使用作了比较详细的介绍。
2.1 resolve & reject
Promise构造函数用来构造一个Promise对象,其中入参匿名函数中resolve
和reject
这两个也都是函数。若是resolve
执行了,则触发promise.then中成功的回调函数;若是reject
执行了,则触发promise.then中拒绝的回调函数。
var promise = new Promise(function(resolve, reject) {
// IF 若是符合预期条件,调用resolve
resolve('success');
// ELSE 若是不符合预期条件,调用reject
reject('failure')
})
复制代码
2.2 Fulfilled & Rejected
Promise对象一开始的值是Pending准备状态。
执行了resolve()
后,该Promise对象的状态值变为onFulfilled状态。
执行了reject()
后,该Promise对象的状态值变为onRejected状态。
Promise对象的状态值一旦肯定(onFulfilled或onRejected),就不会再改变。即不会从onFulfilled转为onRejected,或者从onRejected转为onFulfilled。
2.3 快捷方法
获取一个onFulfilled状态的Promise对象:
Promise.resolve(1);
// 等价于
new Promise((resolve) => resolve(1));
复制代码
获取一个onRejected状态的Promise对象:
Promise.reject(new Error("BOOM"))
// 等价于
new Promise((resolve, reject)
=> reject(new Error("BOOM")));
复制代码
更多快捷方法请参考Promise API。
Promise的异常捕获有两种方式:
then
匿名函数中的reject
方法catch
方法3.1 then中的reject方法捕获异常
这种方法只能捕获前一个Promise对象中的异常,即调用then
函数的Promise对象中出现的异常。
var promise = Promise.resolve();
promise.then(function() {
throw new Error("BOOM!")
}).then(function (success) {
console.log(success);
}, function (error) {
// 捕捉的是第一个then返回的Promise对象的错误
console.log(error);
});
复制代码
但该种方法没法捕捉当前Promise对象的异常,如:
var promise = Promise.resolve();
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}, function (error) {
console.log(error); // 没法捕捉当前then中抛出的异常
});
复制代码
3.2 catch捕获异常
上述栗子若改写成以下形式,最后追加一个catch函数,则能够正常捕捉到异常。
var promise = Promise.resolve();
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}).catch(function (error) {
console.log(error); // 能够正常捕捉到异常
});
复制代码
catch
方法能够捕获到then
中抛出的错误,也能捕获前面Promise抛出的错误。 所以建议都经过catch
方法捕捉异常。
var promise = Promise.reject("BOOM!");
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}).catch(function (error) {
console.log(error); // BOOM!
});
复制代码
值得注意的是:catch
方法其实等价于then(null, reject)
,上面能够写成:
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}).then(null, function(error) {
console.log(error);
})
复制代码
总结来讲就是:
使用promise.then(onFulfilled, onRejected)
的话,在 onFulfilled
中发生异常的话,在onRejected
中是捕获不到这个异常的。
在promise.then(onFulfilled).catch(onRejected)
的状况下then
中产生的异常能在.catch
中捕获
.then
和 .catch
在本质上是没有区别的须要分场合使用。
了解一个东西最好的方式就是尝试本身实现它,尽管可能不少地方不完整,但对理解内在的运行原理是颇有帮助的。
这里主要引用了JavaScript Promises ... In Wicked Detail这篇文章的实现,如下内容主要是对该篇文章的翻译。
4.1 初步实现
首先实现一个简单的Promise对象类型。只包含最基本的then
方法和resolve
方法,reject
方法暂时不考虑。
function Promise(fn) {
// 设置回调函数
var callback = null;
// 设置then方法
this.then = function (cb) {
callback = cb;
};
// 定义resolve方法
function resolve(value) {
// 这里强制resolve的执行在下一个Event Loop中执行
// 即在调用了then方法后设置完callback函数,否则callback为null
setTimeout(function () {
callback(value);
}, 1);
}
// 运行new Promise时传入的函数,入参是resolve
// 按照以前讲述的,传入的匿名函数有两个方法,resolve和reject
fn(resolve);
}
function doSomething() {
return new Promise(function (resolve) {
var value = 42;
resolve(value);
});
}
// 调用本身的Promise
doSomething().then(function (value) {
console.log("got a value", value);
});
复制代码
好了,这是一个很粗略版的Promise。这个实现连Promise须要的三种状态都还没实现。这个版本主要直观展现了Promise的核心方法:then
和resolve
。
该版本若是then
异步调用的话,仍是会致使Promise中的callback为null。
var promise = doSomething();
setTimeout(function() {
promise.then(function(value) {
console.log("got a value", value);
})}, 1);
复制代码
后续经过加入状态来维护Promise,就能够解决这种问题。
4.2 Promise添加状态
经过添加一个字段state
用来维护Promise的状态,当执行了resolve
函数后,修改state
为resolved
,初始state
是pendding
。
function Promise(fn) {
var state = 'pending'; // 维护Promise实例的状态
var value;
var deferred; // 在状态还处于pending时用于保存回调函数的引用
function resolve(newValue) {
value = newValue;
state = 'resolved';
if (deferred) {
// deferred 有值代表回调已经设置了,调用handle方法处理回调函数
handle(deferred);
}
}
// handle方法经过判断state选择如何执行回调函数
function handle(onResolved) {
// 若是还处于pending状态,则先保存then传入的回调函数
if (state === 'pending') {
deferred = onResolved;
return;
}
onResolved(value);
}
this.then = function (onResolved) {
// 对then传入的回调函数,调用handle去执行回调函数
handle(onResolved);
};
fn(resolve);
}
function doSomething() {
return new Promise(function (resolve) {
var value = 42;
resolve(value);
});
}
doSomething().then(function (value) {
console.log("got a value", value);
});
复制代码
加入了状态后,能够经过判断状态来解决调用前后顺序的问题:
在resolve()
执行前调用then()
。代表这时尚未value处理好,这时的状态就是pending
,此时先保留then()
传入的回调函数,等调用resolve()
处理好value值后再执行回调函数,此时回调函数保存在deferred
中。
在resolve()
执行后调用then()
。代表这时value已经经过resolve()
处理完成了。当调用then()
时就能够经过调用传入的回调函数处理value值。
该版本的Promise咱们能够随意先调用resolve()
或pending()
,二者的顺序对程序的执行不会形成影响了。
4.3 Promise添加调用链
Promise是能够链式调用的,每次调用then()
后都返回一个新的Promise实例,所以要修改以前实现的then()
方法。
function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;
function resolve(newValue) {
value = newValue;
state = 'resolved';
if (deferred) {
handle(deferred);
}
}
// 此时传入的参数是一个对象
function handle(handler) {
if (state === 'pending') {
deferred = handler;
return;
}
// 若是then没有传入回调函数
// 则直接执行resolve解析value值
if (!handler.onResolved) {
handler.resolve(value);
return;
}
// 获取前一个then回调函数中的解析值
var ret = handler.onResolved(value);
handler.resolve(ret);
}
// 返回一个新的Promise实例
// 该实例匿名函数中执行handle方法,该方法传入一个对象
// 包含了传入的回调函数和resolve方法的引用
this.then = function (onResolved) {
return new Promise(function (resolve) {
handle({
onResolved: onResolved, // 引用上一个Promise实例then传入的回调
resolve: resolve
});
});
};
fn(resolve);
}
function doSomething() {
return new Promise(function (resolve) {
var value = 42;
resolve(value);
});
}
// 第一个then的返回值做为第二个then匿名函数的入参
doSomething().then(function (firstResult) {
console.log("first result", firstResult);
return 88;
}).then(function (secondResult) {
console.log("second result", secondResult);
});
复制代码
then
中是否传入回调函数也是可选的,如:
doSomething().then().then(function(result) {
console.log('got a result', result);
});
复制代码
在handle()
方法的实现中,若是没有回调函数,直接解析已有的value值,该值是上一个Promise实例中调用resolve(value)
中传入的。
if(!handler.onResolved) {
handler.resolve(value);
return;
}
复制代码
若是回调函数中返回的是一个Promise对象而不是一个具体数值怎么办?此时咱们须要对返回的Promise调用then()
方法。
doSomething().then(function(result) {
// doSomethingElse returns a promise
return doSomethingElse(result);
}).then(function(anotherPromise) {
anotherPromise.then(function(finalResult) {
console.log("the final result is", finalResult);
});
});
复制代码
每次这样写很麻烦,咱们能够在咱们的Promise中的resole()
方法内处理掉这种状况。
function resolve(newValue) {
// 经过判断是否有then方法判断其是不是Promise对象
if (newValue && typeof newValue.then === 'function') {
// 递归执行resolve方法直至解析出值出来,
// 经过handler.onResolved(value)解析出值,这里handler.onResolve就是resolve方法
newValue.then(resolve);
return;
}
state = 'resolved';
value = newValue;
if (deferred) {
handle(deferred);
}
}
复制代码
4.4 Promise添加reject处理
直至目前为止,已经有了一个比较像样的Promise了,如今添加一开始忽略的reject()
方法,使得咱们能够这样使用Promise。
doSomething().then(function(value) {
console.log('Success!', value);
}, function(error) {
console.log('Uh oh', error);
});
复制代码
实现也很简单,reject()
方法与resolve()
方法相似。
function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;
function resolve(newValue) {
if (newValue && typeof newValue.then === 'function') {
newValue.then(resolve, reject);
return;
}
state = 'resolved';
value = newValue;
if (deferred) {
handle(deferred);
}
}
// 添加的reject方法,这里将Promise实例的状态设为rejected
function reject(reason) {
state = 'rejected';
value = reason;
if (deferred) {
handle(deferred);
}
}
function handle(handler) {
if (state === 'pending') {
deferred = handler;
return;
}
var handlerCallback;
// 添加state对于rejected状态的判断
if (state === 'resolved') {
handlerCallback = handler.onResolved;
} else {
handlerCallback = handler.onRejected;
}
if (!handlerCallback) {
if (state === 'resolved') {
handler.resolve(value);
} else {
handler.reject(value);
}
return;
}
var ret = handlerCallback(value);
handler.resolve(ret);
}
this.then = function (onResolved, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onResolved: onResolved,
onRejected: onRejected,
resolve: resolve,
reject: reject
});
});
};
fn(resolve, reject);
}
function doSomething() {
return new Promise(function (resolve, reject) {
var reason = "uh oh, something bad happened";
reject(reason);
});
}
// 调用栗子
doSomething().then(function (firstResult) {
// wont get in here
console.log("first result:", firstResult);
}, function (error) {
console.log("got an error:", error);
});
复制代码
目前咱们的异常处理机制只能处理本身抛出的异常信息,对于其余的一些异常信息是没法正常捕获的,如在resolve()
方法中抛出的异常。咱们对此作以下修改:
function resolve(newValue) {
try {
// ... as before
} catch(e) {
reject(e);
}
}
复制代码
这里经过添加try catch
手动捕获可能出现的异常,并在catch
中调用reject()
方法进行处理。一样对于回调函数,执行时也可能出现异常,也须要作一样的处理。
function handle(deferred) {
// ... as before
var ret;
try {
ret = handlerCallback(value);
} catch(e) {
handler.reject(e);
return;
}
handler.resolve(ret);
}
复制代码
上述完整的演示代码请查看原文做者提供的fiddle。
4.4 Promise保证异步处理
到目前为止,咱们的Promise已经实现了基本比较完善的功能了。这里还有一点须要注意的是,Promise规范提出不论是resolve()
仍是reject()
,执行都必须保持异步处理。要实现这一点很简单,只需作以下修改便可:
function handle(handler) {
if(state === 'pending') {
deferred = handler;
return;
}
setTimeout(function() {
// ... as before
}, 1);
}
复制代码
问题是为何要这么处理?这主要是为了保证代码执行流程的一致性和可靠性。考虑以下栗子:
var promise = doAnOperation();
invokeSomething();
promise.then(wrapItAllUp);
invokeSomethingElse();
复制代码
经过代码的意图应该是但愿invokeSomething()
和invokeSomethingElse()
都执行完后,再执行回调函数wrapItAllUp()
。若是Promise的resolve()
处理不是异步的话,则执行顺序变为invokeSomething()
-> wrapItAllUp()
-> invokeSomethingElse()
,跟预想的产生不一致。
为了保证这种执行顺序的一致性,Promise规范要求resolve
必须是异步处理的。
到这一步,咱们的Promise基本像模像样了。固然离真正的Promise还有一段差距,好比缺少了经常使用的便捷方法如all()
,race()
等。不过本例子实现的方法原本就是从理解Promise原理出发的,相信经过该例子对Promise原理会有比较深刻的了解。
参考