异步在实践开发中无处不在,无论是AJAX还是图片加载,都涉及异步,尽管可以通过回调函数去解决,但是却不够“优雅”。而Promise正是“优雅”的解决方法。Promise的本质是一个对象,可以简单得理解成这个对象有两个属性 status和value, status保存这个Promise的对象的状态,pedding还是fulfilled还是reject,value是resolve的参数。状态改变时会调用resolve或者reject。
1.promise实现ajax
定义一个getJSON函数
getJSON(url) {
return new Promise(function (resolve, reject) {
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);//这里改一下就是POST了
XHR.send();
XHR.onreadystatechange = function () {
if (XHR.readyState === 4) {
if (XHR.status === 200) {
try {
let response = JSON.parse(XHR.responseText);
resolve(response);
} catch (e) {
reject(e);
}
} else {
reject(new Error(XHR.statusText));
}
}
}
})
}
调用时传入url即可,在then里面的两个函数分别对应resolve和reject
this.getJSON('https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10')
.then((value) => {
console.log(value)
},(err) => console.log(err))
2.promise实现图片加载
以前做项目要做懒加载和预加载,那时候就对图片加载有了比较好的认识,这次也用promise来尝试一下
首先定义一个img标签 img属性设置为空
<img src = {this.state.imgSrc} alt = '图片加载中' />
下面是javascript代码
loadImage(src) {
return new Promise((resolve ,reject) => {
const img = new Image();
console.log(new Date().getSeconds(),'刚加载')
img.onload = () => {
resolve(src)
}
img.onerror = () => {
reject('error')
}
img.src = src
})
}
this.loadImage('http://39.108.135.222/upload/8238.jpg')
.then((src) => {
console.log(new Date().toISOString(),'加载成功')
this.setState({imgSrc:src})
},(err) => {
console.log(err)
})
用new Date().toISOString()打印出时间戳 能更好的理解图片异步加载的整个过程
3.promise链式调用
这才是promise优雅的原因 链式调用可以让代码有很好的可读性。这里用setTimeout()测试
delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('延迟了'+ms,'ms')
resolve(--ms)
}, ms);
})
}
this.delay(1000)
.then((ms) => this.delay(ms))//这里是return 一个新的promise
.then(() => console.log('end'))
第二个then调用的是上一个then return出来的promise
4.Promise.all
all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。
const promiseArr = ['300','450','620','111'].map((ms ,i) => this.delay(ms))
Promise.all(promiseArr)//promiseArr中有一个被rejected,Promise.all状态就变成rejected
.then(()=> console.log('all end'))
同样用上面的delay做测试 当所有的的promise执行完毕,就执行resolve
5.Promise与事件循环
我把上面的测试打印出来
不难看出对于 Promise.all里面的promise 是同时执行的
而promise链式调用是顺序执行的
事件循环其实是就是任务队列的执行,任务队列又分为macro-task(宏任务)与micro-task(微任务),例如setTimeout()就是宏任务,而Promise则是微任务
事件循环的顺序,决定了JavaScript代码的执行顺序。它从第一行代码开始第一次循环。执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
6. 一个封装Promise的函数返回new Promise,Promise.resolve()和new Prmise().then().catch()的区别
var func1 = function (aa) {
return new Promise(function (resolve, reject) {
setTimeout(function() {
resolve('data2')
}, 1000)
})
.then(function (value1){
console.log('value1 : ',value1) //'data2'
return aa
})
}
func1('tomato').then(function (value) {
console.log('value2 is :', value) // tomato
})
new Promise返回当前promise,状态改变时才调用then里面的函数。
Promise.resolve(data)把data变成Promise对象,then立即调用,then中函数的参数值就是Promise.resolve的参数data。
new Prmise().then().catch()如上面例子,已经变成链式调用了。其实返回的还是Promise。
7.个人心得
总而言之,我们可以理解为promise有属性status和value,status保存状态,这个状态是会自动改变的。改变时触发resolve 或reject,一般resolve对应then,reject对应catch。(then也可以传reject,不建议)。then(function(value))里面的value就是promise的属性value。(promise没有value为underfined) 。
promise的value从哪里来呢? Promise.resolve()或者new Promise((resolve,reject) => {resolve(value)})
then的返回值,返回的值不是promise对象会自动加上Promise.resolve(returnValue)
参考文章 《ES6 标准入门(第3版)》http://es6.ruanyifeng.com/#docs/promise