最近准备系统深入的学习一下promise这个东西,将从它的概念,使用场景,优势等方面做一个系统的总结。
先总结下每次遇到的关于promise的几种题型,比如判断promise打印顺序,实现合并发送两个请求,发送多个请求,且有依赖关系,如何用promise更好的实现,是否可以封装成队列?
场景一、实现每隔1s打印一个参数
function runAsync1 () {
var p1 = new Promise((resolve, reject) => {
return setTimeout(() => {
console.log(1);
resolve('成功1');
}, 1000);
});
return p1;
}
function runAsync2 () {
var p2 = new Promise((resolve, reject) => {
return setTimeout(() => {
console.log(2);
resolve('成功2');
}, 2000);
})
return p2;
}
function runAsync3 () {
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3);
resolve('成功3');
}, 3000);
})
return p3;
}
runAsync1().then(value => {
console.log(value);
return runAsync2();
}).then(value => {
console.log(value);
return runAsync3();
}).then(value => {
console.log(value);
});
上面的代码实现了每隔1s执行一次回调函数,promise接受一个函数作为参数,函数里接受两个参数:resolve, reject。
这两个参数也是函数,用于分别处理异步操作成功或失败的结果。promise对象还有then/catch等方法,then方法对应处理promise对象中resolve弹出的数据。catch方法对应处理promise对象中reject弹出的数据。
我们看到上面的例子中,每个setTimeout还执行了resolve的函数,这里如果不执行resolve函数会发现,promise无法将链式的操作进行下去。这是为什么呢?这就要说说resolve,和reject都做了什么。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
看了官方解释,我们就知道了resolve会将异步操作的结果作为参数传递出去,通过promise的then方法接收。没有resolve,代码就不会执行到then方法里去。
了解了resolve的意义,再回到上面的需求场景中,就明白了没有resolve,这个promise对象就没有从pending变成resolved,自然就不会走到后面的then方法咯。再说回需求,每隔1s打印一个参数,我们可以用setTimeout来实现间隔1s打印参数的操作,并将它包装成一个promise对象。由于promise在构建之后会立即执行,所以一般被包在函数里。通常在考察eventloop中,也经常用promise的这一特性混淆视听,所以需要注意这个特点哦!
关于promise,setTimeout打印顺序的问题:
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
new Promise((resolve, reject) => {
console.log(3);
setTimeout(() => {
console.log(4);
resolve();
}, 0);
console.log(5);
});
console.log(6);
// 1,3,5,6,undefined,2,4
场景二、实现并发两个请求
async function getData1(u1) {
let data = await fetch(u1);
return data;
}
async function getData1(u2) {
let data = await fetch(u1);
return data;
}
promise.All([getData1(), getData2()]).then(dataArr => {
console.log(dataArr);//dataArr是一个数组,dataArr[0]是getData1()的数据,dataArr[1]是getData2()的数据。
}).catch(err => {
console.log(err);
});
上面的代码不太想的清resolve是什么含义,简单说下自己的理解吧,首先复习一下setTimeout的语法。
回顾setTimeout使用方法:
setTimeout eg1:
function test(ms) {
var p = new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
return p;
}
test(100).then(value => {
alert(value);
});
setTimeout eg2:
var code = function (arr) {
console.log(arr.reduce((a, b) => (a + b));
}
setTimeout(code, 1000, [1,2,3]); //6;1000ms后将数组[1,2,3]传入code函数并执行code回调函数。
setTimeout接收一个代码串,code/function,第二个参数是milliseconds,调用code需要等待的时间,既milliseconds时间后,调用执行code代码串,或调用function,第三个参数是传入执行函数的参数。
如此就可以解释上面的代码啦,经过ms毫秒后,调用resolve函数,并将’done’传入给resolve函数。
场景三、用promise封装一个ajax
function getJson(url) {
var promise = new Promise((resolve, reject) => {
const client = new XMLHttpRequest();
client.open('GET', url); //用get方式请求url;
client.onreadychangestate = function () {
if (~~this.readyState !== 4) {
return;
}
if (~this.status === 200) {
resolve(this.response);
} else {
reject(new Error('出错啦'));
}
}
client.send();//发送
});
}
getJson('hyt/data/image').then(json => {
console.log(json);
}, err => {
console.log('出错啦'+ err);
})
简单说下封装原理,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。
将ajax这样的异步操作包装成promise对象,写在getJson函数里调用,当ajax异步操作完成后,或执行resolve回调函数,并弹出response,跳到then方法里。或执行reject回调函数,并弹出所带参数,跳到then方法里,执行相应操作,(咦?这里有个问题,then方法接收两个函数参数,它们分别有什么意义呢?)至此,简单的ajax封装就完成啦。
关于刚才的疑问,可以在官网中找到答案:
Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
这里想说一下XMLHttpRequest对象包含很多属性方法,这些属性方法帮助我们实现发送一个http请求,得到response或请求失败的信息,从而完成请求的过程。需要花时间梳理一下http请求的过程,几个状态码的含义。才能封装出一个完整的http请求哦!(我实在是不想梳理!不想背哇!)。
场景四、一个异步操作的结果是返回另一个异步操作
如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。
注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
promise还有其他的api:all、race、try、catch、finally…鉴于时间有限,就先不一一展开啦!
当然找时间还要研究一下ES6其他的几个重要概念。
1、weakmap
2、generator
3、async await
4、class
5、扩展运算符