前端近年的兴起,有大部分是因为 NodeJS
的诞生,而 NodeJS
是个适用于 异步IO 密集型的语言,一些基于 NodeJS
的框架,比如 KOA2、Adonis 就有大量的 async
和 await
语法,async
的函数的返回值就是 Promise
对象,我们可以用 async
和 await
语法,写出优雅的异步代码,来替换难看且难维护的回调函数。
Promise 概念(JS的 Promise是未来事件的表示)
Promise
是一种对异步操作的封装,主流的规范是Promise/A+。
Promise
可以使得异步代码层次清晰,便于理解,且更加容易维护。 Promise 可以以成功结束:用行话说我们已经解决了resolved(fulfilled)。 但如果 Promise 出错,我们会说它处于拒绝(rejected )状态。 Promise 也有一个默认状态:每个新的 Promise 都以挂起(pending)状态开始。
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
生成实例时回执信作为参数的函数;
resolve
函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject
函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
1 | // 执行顺序 |
接下来我们就用Promise
结合ajax来使用
1 | const getJSON = function(url) { |
这里我们会渐进式的来创建一个 Promise
的实现,如果,你还不了解 Promise
,赶快移步 Promise 了解学习,当然这个实现会符合 Promise/A+ 规范,JavaScript
中有很多第三方的 Promise
库,bluebird 就是一个第三方 Promise
类库,相比其它第三方类库或标准对象来说,其有以下优点:功能更齐全而不臃肿、浏览器兼容性更好,大家可以了解下。
废话不多说,直接开干。。。 😠
定义 Promise 类型
一个简单 Promise
语法,如下
1 | const promise = new Promise(function(resolve, reject) { |
实现 resolve 和 then
首先我们以上 👆 的语法,自己定义一个 Promise
实例
1 | function Promise(fn) { |
一个简单的实例写好了,然后,来用一下,看看 👀 结果如何
1 | const p = new Promise(function(resolve){ |
执行结果是:callback is not a function
改进1:延时resolve,修改 callback 为异步
这里就遇到一个问题:
目前的Promise有一个bug,假如fn中所包含的是同步代码,则resolve会立即执行,callback
还是 null
,我们的代码是同步的,而不是异步的。
如是,想办法解决掉这个问题,就是利用 setTimeout
, 把 callback
加入异步队列
代码如下 👇
1 | function Promise(fn) { |
改进2:注册多个回调函数,并实现then的链式调用
1 | function Promise(fn) { |
改进3:引入状态
1 | function Promise(fn) { |
手动实现一个Promise:
1 | class Promise(){ |
实现Promise.all 以及 race
1 | // 实现Promise.all 以及 race |
嵌套使用
Promise可以嵌套使用,这样可以是多个任务有条不紊地进行,假设p1是一个Promise对象而p2、p3都是能够产生Promise对象的方法(如果直接new那么Promise将会被直接执行),那么你可以这样写,使得他们按照顺序执行,并且可以一次性处理他们产生的错误。
1 | let p1 = new Promise((resolve, reject) => { |
lite Promise
1 | function isFunction(target) { |
如何取消 promise
Promise.race方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 方法一 取消promise方法 promise.race方法
function wrap(p) {
let obj = {};
let p1 = new Promise((resolve, reject) => {
obj.resolve = resolve;
obj.reject = reject;
});
obj.promise = Promise.race([p1, p]);
return obj;
}
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
});
let obj = wrap(promise);
obj.promise.then(res => {
console.log(res);
});
obj.resolve("请求被拦截了");
obj.reject("请求被拒绝了");新包装一个可操控的promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function wrap(p) {
let res = null;
let abort = null;
let p1 = new Promise((resolve, reject) => {
res = resolve;
abort = reject;
});
p1.abort = abort;
p.then(res, abort);
return p1;
}
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
});
let obj = wrap(promise);
obj.then(res => {
console.log(res);
});
obj.abort("请求被拦截");
Promise使用注意点
一般来说,调用
resolve
或reject
以后,Promise
的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。Promise
实例具有then
方法,也就是说,then
方法是定义在原型对象Promise.prototype
上的。它的作用是为Promise
实例添加状态改变时的回调函数。前面说过,then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)。因此可以采用链式写法,即then方法后面再调用另一个then
方法。Promise.prototype.catch
方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。getJSON
方法返回一个Promise
对象,如果该对象状态变为resolved
,则会调用then
方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected
,就会调用catch
方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch
方法捕获。一般来说,不要在then方法里面定义
reject
状态的回调函数(即then的第二个参数),总是使用catch
方法。跟传统的
try/catch
代码块不同的是,如果没有使用catch
方法指定错误处理的回调函数,Promise
对象抛出的错误不会传递到外层代码,即不会有任何反应,Promise
会吃掉错误。Promise 构造函数是同步执行,then方法是异步执行。
.then
或者.catch
的参数期望是函数,传入非函数则会发生值透传。Promise.all中如果有一个抛出异常了会如何处理?
all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中 其它的异步任务的执行。
promise.all并发还是串行 并发的。不过Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。
promise.all失败获取所有请求
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
28function getData(api){
return new Promise((resolve,reject) => {
setTimeout(() => {
var ok = Math.random() > 0.5 // 模拟请求成功或失败
if(ok)
resolve('get ' + api + ' data')
else{
// reject(api + ' fail') // 如果调用reject就会使Promise.all()进行失败回调
resolve('error') // Promise all的时候做判断 如果是error则说明这条请求失败
}
},2000)
})
}
function getDatas(arr){
var promises = arr.map(item => getData(item))
return Promise.all(promises).then(values => {
values.map((v,index) => {
if(v == 'error'){
console.log('第' + (index+1) + '个请求失败')
}else{
console.log(v)
}
})
}).catch(error => {
console.log(error)
})
}
getDatas(['./api1','./api2','./api3','./api4']).then(() => '请求结束')