JavaScript中的异步编程
JavaScript中的异步编程 ---- Promise
了解JavaScript的异步编程之前需要先学习JavaScript的有关运行机制 为了在整体上有个充分的认识,可以先阅读一下知乎上的这篇文章,相对简单便于理解。
# 什么是Promise
Promise最早由社区提出,是用来解决回调地狱的方案,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓的
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 ---- 《ECMAScript 6 入门》
Promise对象在创建后,就会创建内部状态机,Promise对象代表了一个异步操作,而状态机就包含了这个异步操作的状态,pendding,fulfilled,rejected,
在Promise对象中,只会存在上述三种状态中的一种,且为不可逆的,不受外界影响。
# 创建以及使用Promise
在ES6中有原生的Promise构造函数,使用关键字new 来创建一个Promise对象。
const promise = new Promise ((resolve,reject) => { // 构造函数接受一个函数为参数,将这个参数暂名为'fn' ,'fn'又接受两个参数,且均为函数
const flag = Math.random() > .5 ? true : false ;
if(flag) {
console.log('使用resolve将promise状态从pending变为resolved');
resolve('大于0.5')
} else {
console.log('使用reject将promise状态从pending变为rejected')
reject('小于0.5')
}
})
promise.then((resolve) => {
console.log(resolve) // -- '大于0.5'
},(reject) => {
console.log(reject) // -- '小于0.5'
})
我们来一点点拆分这个Promise,她接收了一个函数,而这个函数又有两个参数resolve,reject,这两个参数都是Promise为我们创建好的函数。这两个函数的作用就是将Promise的状态从pending(等待)转换为resolved(已解决)或者从pending(等待)转换为rejected(已失败)。
而在Promise创建后,有一些自身的方法。这里先只说then,该方法接受两个函数作为参数,这里就和上面传入Promise的构造函数中的参数函数(fn)的两个参数(resolve,reject)对应上了。很显然,then方法的第一个参数函数,接收到resolve传递出的数据,而第二个参数就接收到了reject传递出的数据。
那么Promise是怎么解决异步操作的回调地狱呢?简单的说就是用Promsie对象包裹一个异步操作,异步操作只在Promise对象内部,不受外部影响同时也不阻塞外部,在这个异步操作有了结果后,通过then方法和外部进行数据传递。如下:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('success') : reject('fail');
}, 1000)
});
promise.then((result) => {
console.log(result);
}, (err) => {
console.log(err);
});
这里的setTimeout就是一个异步函数,在1秒后得到结果。若大于0.5的话,resolve就会执行,则Promise的状态从pending变为fulfilled,否则变为rejected,异步操作中的数据(‘success’或‘fail’)会通过函数传递到Promise外部,我们通过then获取到该数据.
那么简单的Promise对象我们已经了解了,但是他是怎么来解决回答地狱的呢?其实很简单,因为then方法会return一个Promise对象来供我们使用.同时我们也可以手动创建新的Promise对象.
举个🌰
const pm1 = new Promise((resolve,reject) => {
resolve('假装这是异步操作的成功的结果')
});
// @1 使用return 默认的Promise对象
pm1.then(result => {
console.log('接收Promise传出的:', result);
return '我是被then return出来的';
}).then(result => {
console.log("接收上一个then:", result)
return "我还是被then return出来的";
}).then(result => {
console.log('接收上一个then:', result)
});
---控制台打印如下
接收Promise传出的: 假装这是异步操作的成功的结果
接收上一个then: 我是被then return出来的
接收上一个then: 我还是被then return出来的
Promise {<resolved>: undefined}
---
// @2 手动创建Promise
pm1.then(result => {
console.log("pm1异步:",result);
const pm2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('pm2异步操作成功的结果')
},2000)
});
return pm2;
}).then(result2 => {
console.log("pm2异步:",result2)
})
--- 控制台打印如下
pm1异步: 假装这是异步操作的成功的结果
Promise {<pending>}
// 2s后
pm2异步: pm2异步操作成功的结果
---
这就是常见的Promis的链式调用,其实Promise就是原生封装的一个对象,约定创建是传入两个方法分别在成功和失败后调用,又在then的两参数(均为函数)中获取.下面简单介绍一下catch和其他原生方法的使用场景.
# catch
一句话来解释,catch === then(null,reject).
catch是用来捕捉Promis中的异常,也就是失败后传递出的信息.then是可以同时接受失败和成功后Promise从内部传出的数据,但是未来方便阅读和维护,一般在then中只执行成功的.我们将整个过程中的异常放到最后,通过catch来捕获.再举个🌰:
const pmCatch = new Promise((resolve,reject) => {
reject('fail')
});
pmCatch.then(result => {
console.log('this is then');
const pmCatch2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('success')
},2000)
});
return pmCatch2;
}).then(res => {
cpnsole.log('then:', res);
}).catch(err => {
console.log('catch:', err);
})
---控制台打印如下
catch: fail
Promise {<resolved>: undefined}
---
看下打印结果,好像跟想象中的不太一样.这里没有打印出‘this is then’和‘then:success’,而是直接打印了‘catch: fail’.明显的在整个Promise链中,只要出现了reject了,整个Promise的状态就会凝固且不会变化.另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。而且Promise对象对于异常有类似冒泡的性质,异常会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
# Promise.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
# Promise.all()
Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例.
const p = Promise.all([p1, p2, p3]); // p1 p2 p3 均为Promise对象
Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。
p的状态由p1、p2、p3决定,分成两种情况。
- 只有
p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 - 只要
p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
一般的使用场景是数据同时依赖于多个请求的返回值时.
# Promis.race()
Promise.race()方法同样是将多个Promise实例,包装成一个新的Promise实例。其参数格式同Promise.all();
const p = Promise.all([p1, p2, p3]); // p1 p2 p3 均为Promise对象
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。“行”如其名,竞跑.谁最早跑完听谁的.
# Promise.any()
Promise.any()方法接受一组Promise实例作为参数,包装成一个新的Promise实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
# 其他
其他的还有Promise.resolve(),Promise.reject(),以上两个方法详情参考阮大的《ES6入门》