最近看到一篇有关promise的文章还不错,想着翻译过来看看,顺便也算是对promise作个总结。
好了,开门见山,跟着我的脚步一起来学习吧。
一、最简单的例子
1、先看例子1:
function doSomething(callback) {
var value = 42;
callback(value);
}
doSomething(function(value) {
console.log('Got a value:' + value);
});复制代码
这是一个传入函数作为回调函数的函数,调用回调函数得到value值。
需要做些什么改变让它看起来像‘promise’呢?来看例子2
2、例子2:
function doSomething() {
return {
then: function(callback) {
var value = 42;
callback(value);
}
};
}
doSomething().then(function(value) {
console.log('Got a value:' + value);
});复制代码
例2是在例1的基础上做修改:在函数体内直接返回一个对象,然后对象里面有then,其value为function。
使用上是在.then()里传入一个回调函数。并且得到then中的value(42)。这使用看起来和promise好像一样了。
promise有个重要的思想:Promises capture the notion of an eventual value into an object。
这句话的意思是promise捕捉最终值传入到一个对象中。
读到这里也许有些困惑,没关系,一起继续探索强大有趣的事情吧!!!(老外的语言就是这么直白简单hhhhhh...)
二、定义Promise类
1、现在开始,进入正题。定义一个简单的Promise类。
function Promise(fn) {
var callback = null;
this.then = function(cb) {
callback = cb;
};
function resolve(value) {
callback(value);
}
fn(resolve);
}
function doSomething() {
return new Promise(function(resolve) {
var value = 42;
resolve(value);
});
}复制代码
来调用一下:
doSomething().then(function(res){
console.log(res);
})
//来分析一下执行顺序:
// doSomething -> new Promise -> fn -> resolve -> callback复制代码
emm...执行到这里,报错了?? callback是null
为什么呢? 因为callback是在then()方法中被赋值的,现在resolve()早于then()执行,callback还是null的状态。
那怎么办呢?
我们用setTimeout来解决这个问题。
function Promise(fn) {
var callback = null;
this.then = function(cb) {
callback = cb;
};
function resolve(value) {
// force callback to be called in the next
// iteration of the event loop, giving
// callback a chance to be set by then()
setTimeout(function() {
callback(value);
}, 1);
}
fn(resolve);
}复制代码
好了 现在我们再来分析下执行过程。
doSomething -> new Promise -> fn -> resolve -> then -> callback复制代码
这样是顺序能够正常运行了,但是我觉得这个代码糟糕透了
来下面的例子:
var promise = doSomething()
setTimeout(function() {
promise.then(function(value) {
log("got a value", value);
});
复制代码
如果我们是这样调用呢,是不是又要报错了!
callback和then()先后被放入异步执行队列中,根据先进先出原则,callback先执行,可此时callback is not a function but null.
我们的这个尝试以失败告终,接着来...
2、给Promise添加状态
- A promise can be **pending** waiting for a value, or **resolved** with a value.
- Once a promise resolves to a value, it will always remain at that value and never resolve again.
这两句话的意思是一个promise要么处于pending状态(等待结果),要么已经resolved(得到结果),promise一旦resolved得到结果,它就一直保持这个结果不会再执行resolve。
(promise的reject稍后讨论)
function Promise(fn) {
var state = 'pending';
var value;
var deferred;
function resolve(newValue) {
value = newValue;
state = 'resolved';
if(deferred) {
handle(deferred);
}
}
function handle(onResolved) {
if(state === 'pending') {
deferred = onResolved;
return;
}
onResolved(value);
}
this.then = function(onResolved) {
handle(onResolved);
};
fn(resolve);
}
function doSomething() {
return new Promise(function(resolve) {
var value = 42;
resolve(value);
});
}
function doSomethingElse() {
return new Promise(function(resolve) {
var value = 3;
setTimeout(function(){
resolve(value);
},1);
});
}
复制代码
我们来调用一下:
测试1:
doSomething().then(function(value) {
log("got a value", value);
});
//分析一下过程:
doSomething -> Promise -> fn -> resolve -> then -> handle -> onResolved复制代码
测试2:
var promise = doSomething();
setTimeout(function(){
promise.then(function(value){
log("got a value", value);
})
})
//过程分析
doSomething -> Promise -> fn -> resolve -> then -> handle -> onResolved复制代码
测试3:
var promiseElse = doSomethingElse();
promiseElse.then(function(value){
log("got a value", value);
});
//过程分析
doSomethingElse -> Promise -> fn -> then -> handle(deferred缓存onResolved)
->setTimeout异步队列开始执行,resolve执行 -> handle(deferred)复制代码
以上测试无论何时调用then()和resolve(),根据state状态的判断,可以收放自如地完成同步异步函数执行。
With promises, the order in which we work with them doesn't matter. We are free to call `then()` and `resolve()` whenever they suit our purposes. This is one of the powerful advantages of capturing the notion of eventual results into an object
显然,到这里我们已经实现了Promise强大的pending-->resolved功能。
接下来我们要干什么呢?
三、链式Promises
平时我们对Promise的链式调用使用得很多吧,即使没使用过链式调用那也一定看到过哦。
`then()` always returns a promise
根据这个原则,我们稍作修改了代码
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;
}
if(!handler.onResolved) {
handler.resolve(value);
return;
}
var ret = handler.onResolved(value);
handler.resolve(ret);
}
this.then = function(onResolved) {
return new Promise(function(resolve) {
handle({
onResolved: onResolved,
resolve: resolve
});
});
};
fn(resolve);
}复制代码
这里我们注意到:then()返回以个新的Promise对象,这使得我们在实例化一个Promise对象之后调用then()时还可以接着调用then(),形成了链式调用。
下面我们来看下测试用例:
doSomething().then(function(result) {
console.log('first result', result);
return 88;
}).then(function(secondResult) {
console.log('second result', secondResult);
});
// first result 42
// second result 88复制代码
1、参数可选
当然then()方法里面的参数是可选,我们也可能遇到这样情况:
doSomething().then().then(function(result) {
console.log('got a result', result);
});
// got a result 42
//这里第一个then没有回调函数 但是第二个then还是能得到结果
//执行顺序:
doSomething -> Promise -> fn -> resolve -> then -> handle -> handler.resolve(42) -> then
-> handle -> handler.onResolve(42) -> handler.resolve(undefined)复制代码
这里的第一个then没有传入回调函数,所以直接执行handler.resolve()将value保存以待第二个then获取。
2、在链中返回Promise
doSomething().then(function(result) {
// doSomethingElse returns a promise
return doSomethingElse(result);
}).then(function(finalResult) {
console.log("the final result is", finalResult);
});复制代码
这里的finalResult按照预期的结果是返回了Promise实例,但是如果我们想要doSomethingElse中resolved值怎么办?
修改一下resolve函数
function resolve(newValue) {
if(newValue && typeof newValue.then === 'function') {
newValue.then(resolve);
return;
}
state = 'resolved';
value = newValue;
if(deferred) {
handle(deferred);
}
}复制代码
我们保持resolve()中value值不是一个promise实例而是一个普通值,所以在value赋值之前再做一次.then的操作,直到得到value的值为一个普通值。
附上完整代码,执行一遍。
function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;
function resolve(newValue) {
if (newValue && typeof newValue.then === 'function') {
newValue.then(resolve);
return;
}
state = 'resolved';
value = newValue;
if (deferred) {
handle(deferred);
}
}
function handle(handler) {
if (state === 'pending') {
deferred = handler;
return;
}
if (!handler.onResolved) {
handler.resolve(value);
return;
}
var ret = handler.onResolved(value);
handler.resolve(ret);
}
this.then = function (onResolved) {
return new Promise(function (resolve) {
handle({
onResolved: onResolved,
resolve: resolve
});
});
};
fn(resolve);
}
function doSomething() {
return new Promise(function (resolve) {
var value = 42;
resolve(value);
});
}
function doSomethingElse(value) {
return new Promise(function (resolve) {
resolve("did something else with " + value);
});
}
doSomething().then(function (firstResult) {
log("first result:", firstResult);
return doSomethingElse(firstResult);
}).then(function (secondResult) {
log("second result:", secondResult);
});
//first result: 42//second result: did something else with 42
//执行顺序分析:
doSomething -> Promise -> fn -> resolve -> then -> Promise -> fn -> handle -> handler.onResolved
-> handler.resolve(doSomethingElse(firstResult)) -> newValue.then -> Promise -> fn -> handle(这里的onResolved传入的是上一个then的resolve)
-> handler.onResolved 这里完成对上一个then的value赋值 -> handler-resolve -> then(这里开始第二个then)复制代码
这里过程有点绕,不过慢慢分析可以理清楚这中间value传递。
四、reject处理
终于要加上reject部分的处理啦 其实和resolve部分差不多。当程序运行错误时,我们需要获知为什么产生了错误,那么在promise中我们知道错误的发生呢??
这时需要then的第二个回调函数!
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);
}
}
function reject(reason) {
state = 'rejected';
value = reason;
if(deferred) {
handle(deferred);
}
}
function handle(handler) {
if(state === 'pending') {
deferred = handler;
return;
}
var handlerCallback;
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 result = somehowGetTheValue();
if(result.error) {
reject(result.error);
} else {
resolve(result.value);
}
});
}复制代码
As mentioned earlier, the promise will transition from **pending** to either **resolved** or **rejected**, never both. In other words, only one of the above callbacks ever gets called
上面doSomething调用时,我们发现Promise的回调方法中,如果有错误发生,那么调用reject();如果没有错误发生,那么调用resolve()。Promise会从pending到resolved,或者从pending到rejected,只有这两种情况,而且只能发生其一,也就是说要么成功,要么失败。
意想不到的错误我们也要处理。
刚刚我们调用reject()方法是在我们能知道的情况下,但是万一在promise中发生错误我们应该怎么办呢?
这里有两个采取捕捉错误的方法。
1、在resolve中可能发生错误进行捕捉
function resolve(newValue) {
try {
// ... as before
} catch(e) {
reject(e);
}
}复制代码
2、在handle方法中进行错误捕捉
function handle(deferred) {
// ... as before
var ret;
try {
ret = handlerCallback(value);
} catch(e) {
handler.reject(e);
return;
}
handler.resolve(ret);
}复制代码
文章到这里,reject的部分也完成了。如果你对done()、all()、race()等方法感兴趣你可以去看promise的官方API。
github上有学习的代码:https://github.com/Turboemily/learn_promise_development.git
以上内容来自文章:https://www.mattgreer.org/articles/promises-in-wicked-detail/#defining-the-promise-type。