一、什么是Promise?我们用Promise来解决什么问题?
Promise 是异步编程的一种解决方案: 从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。 promise有三种状态:**pending(等待态),fulfiled(成功态),rejected(失败态)**;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。<–more–>
我相信大家经常写这样的代码:
1 2 3 4 5 6 7 8 9
| function fn1(a, fn2) { if (a > 10 && typeof fn2 == 'function') { fn2() } } fn1(11, function() { console.log('this is a callback') })
|
一般来说我们会碰到的回调嵌套都不会很多,一般就一到两级,但是某些情况下,回调嵌套很多时,代码就会非常繁琐,会给我们的编程带来很多的麻烦,这种情况俗称——回调地狱。
这时候我们的promise就应运而生、粉墨登场了
promise是用来解决两个问题的:
回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
promise可以支持多个并发的请求,获取并发请求中的数据
这个promise可以解决异步的问题,本身不能说promise是异步的
二、es6 promise用法大全
Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。
那就new一个
1 2 3 4 5 6 7
| let p = new Promise((resolve, reject) => { setTimeout(() => { console.log('执行完成'); resolve('我是成功!!'); }, 2000); });
|
Promise的构造函数接收一个参数:函数,并且这个函数需要传入两个参数:
resolve :异步操作执行成功后的回调函数
reject:异步操作执行失败后的回调函数
then 链式操作的用法
所以,从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:
1 2 3 4 5 6 7 8 9
| p.then((data) => { console.log(data); }) .then((data) => { console.log(data); }) .then((data) => { console.log(data); });
|
reject的用法 :
把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。看下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| let p = new Promise((resolve, reject) => { setTimeout(function(){ var num = Math.ceil(Math.random()*10); if(num<=5){ resolve(num); } else{ reject('数字太大了'); } }, 2000); }); p.then((data) => { console.log('resolved',data); },(err) => { console.log('rejected',err); } );
|
then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:
resolve 5
或者rejected 数字太大了
catch的用法
我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调。用法是这样:
1 2 3 4 5
| p.then((data) => { console.log('resolved',data); }).catch((err) => { console.log('rejected',err); });
|
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:
1 2 3 4 5 6 7
| p.then((data) => { console.log('resolved',data); console.log(somedata); }) .catch((err) => { console.log('rejected',err); });
|
在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能
all的用法:谁跑的慢,以谁为准执行回调。all接收一个数组参数,里面的值最终都算返回Promise对象
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。看下面的例子:
1 2 3 4 5 6 7 8 9 10 11
| let Promise1 = new Promise(function(resolve, reject){}) let Promise2 = new Promise(function(resolve, reject){}) let Promise3 = new Promise(function(resolve, reject){})
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(funciton(){ }, function(){ })
|
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
race的用法:谁跑的快,以谁为准执行回调
race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function requestImg(){ var p = new Promise((resolve, reject) => { var img = new Image(); img.onload = function(){ resolve(img); } img.src = '图片的路径'; }); return p; } function timeout(){ var p = new Promise((resolve, reject) => { setTimeout(() => { reject('图片请求超时'); }, 5000); }); return p; } Promise.race([requestImg(), timeout()]).then((data) =>{ console.log(data); }).catch((err) => { console.log(err); });
|
requestImg函数会异步请求一张图片,我把地址写为”图片的路径”,所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:
好了,我相信大家对用法已经懂了,那么我们来手写一款自己的promise吧
三、根据promiseA+实现一个自己的promise
步骤一:实现成功和失败的回调方法
要实现上面代码中的功能,也是promise最基本的功能。首先,需要创建一个构造函数promise,创建一个promisel类,在使用的时候传入了一个执行器executor,executor会传入两个参数:成功(resolve)和失败(reject)。之前说过,只要成功,就不会失败,只要失败就不会成功。所以,默认状态下,在调用成功时,就返回成功态,调用失败时,返回失败态。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Promise { constructor (executor){ this.status = 'panding'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = (data) => { if(this.status === 'pending'){ this.value = data; this.status = "resolved"; this.onResolvedCallbacks.forEach(fn => fn()); }
} let reject = (reason) => { if(this.status === 'pending'){ this.reason = reason; this.status = 'rejected'; this.onRejectedCallbacks.forEach(fn => fn()); } } try{ executor(resolve,reject); }catch (e){ reject(e); }
}
|
promise A+规范规定,在有异常错误时,则执行失败函数。
1 2 3 4 5 6 7
| constructor (executor){ ...... try{ executor(resolve,reject); }catch(e){ reject(e); } }
|
步骤二:then方法链式调用
then方法是promise的最基本的方法,返回的是两个回调,一个成功的回调,一个失败的回调,实现过程如下:
1 2 3 4 5 6 7 8
| then(onFulFilled, onRejected) { if (this.status === 'resolved') { onFulFilled(this.value); } if (this.status === 'rejected') { onRejected(this.reason); } }
|
1 2 3 4 5 6
| let p = new Promise(function(){ resolve('我是成功'); }) p.then((data) => {console.log(data);},(err) => {}); p.then((data) => {console.log(data);},(err) => {}); p.then((data) => {console.log(data);},(err) => {});
|
返回的结果是:
为了实现这样的效果,则上一次的代码将要重新写过,我们可以把每次调用resolve的结果存入一个数组中,每次调用reject的结果存入一个数组。这就是为何会在上面定义两个数组,且分别在resolve()和reject()遍历两个数组的原因。因此,在调用resolve()或者reject()之前,我们在pending状态时,会把多次then中的结果存入数组中,则上面的代码会改变为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| then(onFulFilled, onRejected) { if (this.status === 'resolved') { onFulFilled(this.value); } if (this.status === 'rejected') { onRejected(this.reason); } if (this.status === 'pending') { this.onResolvedCallbacks.push(() => { onFulFilled(this.value); }); this.onRejectedCallbacks.push(() => { onRejected(this.reason); }); } }
|
Promise A+规范中规定then方法可以链式调用
在promise中,要实现链式调用返回的结果是返回一个新的promise,第一次then中返回的结果,无论是成功或失败,都将返回到下一次then中的成功态中,但在第一次then中如果抛出异常错误,则将返回到下一次then中的失败态中
链式调用成功时
链式调用成功会返回值,有多种情况,根据举的例子,大致列出可能会发生的结果。因此将链式调用返回的值单独写一个方法。方法中传入四个参数,分别是p2,x,resolve,reject,p2指的是上一次返回的promise,x表示运行promise返回的结果,resolve和reject是p2的方法。则代码写为:
1 2 3
| function resolvePromise(p2,x,resolve,reject){ .... }
|
1 2 3
| var p = new Promise((resovle,reject) => { return p; })
|
当返回结果是自己时,永远也不会成功或失败,因此当返回自己时,应抛出一个错误
1 2 3 4 5 6
| function resolvePromise(p2,x,resolve,reject){ if(px===x){ return reject(new TypeError('自己引用自己了')); } .... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| function resolvePromise(promise2,x,resolve,reject){ if(promise2 === x){ return reject(new TypeError('循环引用')); }; if(x !=null && (typeof x === 'object' || typeof x === 'function')){ let called; try{ let then = x.then; if(typeof then === 'function'){ then.call(x,y => { if(called) return; called = true; resolvePromise(promise2,y,resolve,reject) },r => { if(called) return; called = true; reject(r); }); }else{ resolve(x); } }catch (e){ if(called) return; called = true; reject(e) } }else{ resolve(x) }
}
|
也就是当调用成功就不能再调用失败了,如果两个都调用的时候,哪个先调用就执行哪一个。代码部分还是上面那部分
个人认为,这个地方比较绕,需要慢慢的一步一步的理清楚。
根据promise A+规范原理,promise在自己的框架中,封装了一系列的内置的方法。
- 捕获错误的方法 catch()
- 解析全部方法 all()
- 竞赛 race()
- 生成一个成功的promise resolve()
- 生成一个失败的promise reject()
最后给大家附上全部源码,供大家仔细品读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
| function resolvePromise(promise2,x,resolve,reject){ if(promise2 === x){ return reject(new TypeError('循环引用')); }; if(x !=null && (typeof x === 'object' || typeof x === 'function')){ let called; try{ let then = x.then; if(typeof then === 'function'){ then.call(x,y => { if(called) return; called = true; resolvePromise(promise2,y,resolve,reject) },r => { if(called) return; called = true; reject(r); }); }else{ resolve(x); } }catch (e){ if(called) return; called = true; reject(e) } }else{ resolve(x) }
}
class Promise { constructor (executor){ this.status = 'panding'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = (data) => { if(this.status === 'pending'){ this.value = data; this.status = "resolved"; this.onResolvedCallbacks.forEach(fn => fn()); }
} let reject = (reason) => { if(this.status === 'pending'){ this.reason = reason; this.status = 'rejected'; this.onRejectedCallbacks.forEach(fn => fn()); } } try{ executor(resolve,reject); }catch (e){ reject(e); }
} then(onFuiFilled,onRejected){ onFuiFilled = typeof onFuiFilled === 'function' ? onFuiFilled : y => y; onRejected = typeof onRejected === 'function' ? onRejected :err => {throw err;} let promise2; if(this.status === 'resolved'){ promise2 = new Promise((resolve,reject) => { setTimeout(() => { try{ let x = onFuiFilled(this.value); resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e); } },0) }); } if(this.status === 'rejected'){ promise2 = new Promise((resolve,reject) => { setTimeout(() => { try{ let x = onRejected(this.reason); resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e); } },0)
}); } if(this.status === 'pending'){ promise2 = new Promise((resolve,reject) => { this.onResolvedCallbacks.push( () =>{ setTimeout(() => { try{ let x = onFuiFilled(this.value); resolvePromise(promise2,x,resolve,reject); }catch(e){ reject(e); } },0) }); this.onRejectedCallbacks.push( ()=>{ setTimeout(() => { try{ let x = onRejected(this.reason); resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e) } },0) }) }) } return promise2; } catch (onRejected) { return this.then(null, onRejected); } } Promise.all = function (promises) { return new Promise(function (resolve, reject) { let arr = []; let i = 0; function processData(index, data) { arr[index] = data; if (++i === promises.length) { resolve(arr); } } for (let i = 0; i < promises.length; i++) { promises[i].then(function (data) { processData(i, data) }, reject) } }) }
Promise.race = function (promises) { return new Promise((resolve, reject) => { for (var i = 0; i < promises.length; i++) { promises[i].then(resolve,reject) } }) }
Promise.resolve = function(value){ return new Promise((resolve,reject) => resolve(value); }
Promise.reject = function(reason){ return new Promise((resolve,reject) => reject(reason)); } Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise( (resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd } module.exports = Promise;
|
扩展阅读:async和await:让异步编程更简单