1.Promise简介
- Promise是
异步编程的一种解决方案
,比传统的解决方案(回调函数和事件)更合理和更强大; - 所谓Promise,简单说就是一个容器,里面保存着某个
未来才会结束的事件(通常是一个异步操作)的结果
; - 具体表达:
- 从语法上来说: Promise是一个构造函数;
- 从功能上来说: promise对象用来封装一个异步操作并可以获取其结果(数据);
2.Promise对象的两个主要特点
2.1.对象的状态不受外界影响
Promise对象代表一个异步操作,有三种状态:
pending(进行中)、fulfilled/resolved(已成功)和rejected(已失败).
只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态.这也是Promise这个名字的由来,它的英语意思就是"承诺",表示其他手段无法改变;
2.2.一旦状态改变,就不会再变,任何时候都可以得到这个结果
Promise对象的状态改变,只有两种可能:
从pending变为fulfilled和从pending变为rejected.
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型).如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果.这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的;
说明:promise的状态改变
①.pending变为resolved;
②.pending变为rejected;
只有这2种情况,且一个promise对象的状态只能被改变一次;无论变为成功还是失败,都会有一个结果数据;成功的结果数据一般称为vlaue(结果),失败的结果数据一般称为reason(失败的原因);
3.Promise对象的基本语法
3.1.创建一个Promise实例
//通过Promise对象的构造函数创建一个Promise对象实例
//注意:这里new出来的promise对象实例只是代表形式上的一个异步操作所谓形式上的异步操作,就是说我们只知道他是一个异步操作,但是要做什么具体的异步操作,目前还不清楚
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
注意:
①.Promise构造函数接受一个执行器函数作为参数,该执行器函数的两个参数分别是resolve和reject.它们也是两个函数,由JavaScript引擎提供,不用自己部署/定义;
②.Promise创建之后就会立即执行构造函数中的function异步操作;
③.resolve函数的作用:
将Promise对象的状态从"未完成"变为"成功"(即从 pending变为resolved).在异步操作成功时调用,并将异步操作的结果作为参数传递出去;
④.reject函数的作用:
将Promise对象的状态从"未完成"变为"失败"(即从 pending变为rejected).在异步操作失败时调用,并将异步操作抛出的错误,作为参数传递出去;
- 例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>promise01</title>
</head>
<body>
<script type="text/javascript" charset="utf-8">
//创建一个promise实例对象,参数是一个(执行器)函数,里面定义了要执行的异步任务,函数的两个参数也都是函数
const promise = new Promise((resolve, reject) => {
//立刻同步执行/加载异步任务
setTimeout(() => {
const time = Date.now();
if (time % 2 == 0) {
//如果任务执行成功,调用resolve(value)回调函数
resolve('成功的数据:' + time);
} else {
reject('失败的数据:' + time)
//如果任务执行失败,调用reject(reson)回调函数
}
}, 100);
});
//指定成功/失败的回调函数OnResolve的/OnRejected
promise.then(
(value) => { //任务执行成功返回一个结果数据value
console.info(value);
},
(reson) => { //任务执行失败返回一个reson失败原因
console.info(reson);
}
)
</script>
</body>
</html>
结果:
3.3.Promise的基本流程
3.3.Promise API
3.3.1.Promise构造函数:Promise(excutor(resovlve,reject){})
①.excutor函数: 执行器(resolve,reject) => {}
②.resolve函数: 内部定义成功时我们调用的函数:value => {}
③.reject函数: 内部定义失败时我们调用的函数:reason => {}
说明:excutor执行器会在Promise内部立即同步回调/加载,(excutor执行器里面的)异步操作在执行器(excutor)中执行;
3.3.2.Promise.prototype.then方法:(onResolved, onRejected)=> {}
①.onResolved函数: 成功的回调函数:(value) => {}
②.onRejected函数: 失败的回调函数:(reason)=> {}
说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调,返回一个新的promise对象;
注意:
①.then()方法可以接受两个回调函数作为参数.第一个回调函数是Promise对象的状态变为resolved时调用;第二个回调函数是Promise对象的状态变为rejected时调用.其中,第二个函数是可选的,不一定要提供.这两个函数都接受Promise对象传出的值(结果/异常)作为参数;
②.Promise 实例具有then()方法,也就是说,then()方法是定义在原型对象Promise.prototype
上的.它的作用是为Promise实例添加状态改变时的回调函数.前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数;
③.then()方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例).因此可以采用链式写法,即then()方法后面再调用另一个then方法,并且前一个then()里面的回调函数完成以后,会将返回结果作为参数,传入后面的then()里面的回调函数;
④.采用链式的then()方法,可以指定一组按照次序调用的回调函数.这时,前一个回调函数有可能返回的还是一个Promise对象(即有异步操作),而后面一个回调函数,就需要等待该Promise对象的状态发生变化,才会被调用;
3.3.3.Promise.prototype.catch方法:(onRejected) => {}
①.onRejected函数: 失败的回调函数(reason) => {}
说明:then()的语法糖,相当于:then(undefined,onRejected)或者then(undefined,onRejected)
注意
①.如果promise异步操作出现异常/错误,状态就会变为’rejected’,就会调用catch方法指定的回调函数,处理这个错误.另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获;
②.一般来说,不要在then方法里面定义’Rejected’状态的回调函数(即then的第二个参数),总是使用catch()方法,因为catch()可以捕获前面then()方法执行中的错误,也更接近同步的写法(try/catch),并且不会终止catch()后面的then()方法继续执行;
例如:promise .then(function(data) { // success }) .catch(function(err) { // error });
③.一般总是建议,Promise 对象后面要跟catch()方法,这样可以处理Promise内部发生的错误.catch()方法返回的还是一个Promise对象,因此后面还可以接着调用then()方法;
例如:Promise.resolve() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); //carry on });
④.catch()方法之中,还能再抛出错误,可以被后面的catch()方法捕获;
3.3.4.Promise.resolve方法:(value) => {}
①.value: 成功的数据或promise对象
说明:返回一个成功/失败的promise对象
3.3.5.Promise.reject方法:(reason) => {}
①.reason: 失败的原因
说明:返回一个失败的promise对象
3.3.6.Promise.all方法:(promises) => {}
①.promises: 包含n个promise对象的数组;
说明:返回一个新的promise,只有所有的promise都成功才成功,只要有一个失败了就直接失败
3.3.7.Promise.race方法:(promises) => {}
①.promises: 包含n个promise对象的数组;
说明:返回一个新的promise,第一个执行完成的promise的结果状态就是最终的结果状态
更多内容请移步:
promise中文文档
4.为什么要使用Promise?
4.1.指定回调函数的方式更加灵活
1>.传统方式
必须在启动异步任务前指定,例如传统的ajax请求;
2>.Promise
启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个);
4.2.支持链式调用,可以解决回调地狱问题
1>.什么是回调地狱?
回调函数嵌套调用,即外部回调函数异步执行的结果是内部嵌套的回调函数执行的条件;(有点类似于Linux系统中的管道符’|’)
2>.回调地狱的缺点?
①.不便于阅读
②.不便于异常处理
3>.解决方案?
①.promise链式调用(这种方式还是有回调函数)
②.async/await
4>.案例代码:
<script type="text/javascript" charset="utf-8">
//成功的回调函数
function successCallBack(result){
console.info("文件创建成功");
}
//失败的回调函数
function failureCallBack(reson){
console.info("文件创建失败");
}
//使用纯回调函数
createAudioFileAsync(audioSettings,successCallBack,failureCallBack);
//使用promise
const promise = createAudioFileAsync(audioSettings);
setTimeout(() => {
//指定成功/失败的回调函数
promise.then(successCallBack,failureCallBack);
}, 1000);
//回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult)
}, failureCallback)
}, failureCallback)
}, failureCallback)
/*
使用 promise 的链式调用解决回调地狱
*/
doSomething()
.then(function(result) {
return doSomethingElse(result)
})
.then(function(newResult) {
return doThirdThing(newResult)
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult)
})
.catch(failureCallback)
/*
async/await: 回调地狱的终极解决方案
*/
async function request() {
try {
const result = await doSomething()
const newResult = await doSomethingElse(result)
const finalResult = await doThirdThing(newResult)
console.log('Got the final result: ' + finalResult)
} catch (error) {
failureCallback(error)
}
}
</script>
5.Promise的几个关键问题
5.1.如何改变Promise的状态?
①.resolve(value)函数: 如果当前是’pendding’就会变为’resolved’;
②.reject(reason)函数: 如果当前是’pendding’就会变为’rejected’;
③.抛出异常: 如果当前是’pendding’就会变为’rejected’;
- 代码如下:
<script type="text/javascript" charset="utf-8"> const p=new Promise((resolve,reject)=>{ resolve(1); //promise的状态由pending变成了resolved成功状态 reject(2); //promise的状态由pending变成了rejected失败状态 throw new Error('出错了'); //promise的状态由pending变成了rejected失败状态,reason为抛出的error }) </script>
5.2.一个Promise指定多个成功/失败回调函数,都会调用吗?
1>.当Promise改变为对应状态时都会调用
<script type="text/javascript" charset="utf-8">
const p=new Promise((resolve,reject)=>{
resolve(1); //promise的状态由pending变成了resolved成功状态
//reject(2); //promise的状态由pending变成了rejected失败状态
//throw new Error('出错了'); //promise的状态由pending变成了rejected失败状态,reason为抛出的error
})
//指定promise对象回调函数
//一个promise指定了多个回调函数,当promise改变为对应状态时对应的回调函数都会调用
p.then(
(value)=>{
console.info('第一个回调函数:'+value);
},
(reason)=>{
console.log('第一个回调函数:'+reason);
}
)
p.then(
(value)=>{
console.info('第二个回调函数:'+value);
},
(reason)=>{
console.log('第二个回调函数:'+reason);
}
)
</script>
- 输出结果
5.3.改变Promise状态和指定回调函数谁先谁后?
1>.都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调函数;
2>.如何先改状态再指定回调函数?
①.在执行器中直接调用resolve()/reject();
②.延迟更长时间才调用then();
3>.什么时候才能得到数据?
①.如果先指定回调函数,那当状态发生改变时,回调函数就会被调用,得到数据;
②.如果先改变状态,那当指定回调函数时,回调函数就会被调用,得到数据;
-
例子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script type="text/javascript" charset="utf-8"> //先指定回调函数,后改变状态 new Promise((resolve,reject)=>{ setTimeout(() => { resolve(1); //后改变状态(同时指定数据),异步执行回调函数 }, 1000); }).then( //先指定回调函数,保存当前指定的回调函数 value => { console.log(value); }, reason => { console.info(reason); } ) //先改变状态,后指定回调函数 new Promise((resolve,reject) => { resolve(1); //先改变状态(同时指定数据),将promise状态保存起来 }).then( //后指定回调函数,异步执行回调函数 value => { console.info(value); }, reason => { console.info(reason); } ) //先改变状态,后指定回调函数 const p=new Promise((resolve,reject)=>{ setTimeout(() => { resolve(1); //先改变状态(同时指定数据),将promise状态保存起来 }, 1000); }) setTimeout(() => { p.then( //后指定回调函数,异步执行回调函数 value => { console.log(value); }, reason => { console.info(reason); } ) }, 1100); </script> </body> </html>
-
输出结果
5.4.promise.then()返回的新Promise对象的结果状态由什么决定?
1>.简单表达:由then()方法中指定的回调函数执行的结果决定;
2>.详细表达:
①.如果抛出异常,新promise变为rejected,reason为抛出的异常;
②.如果返回的是非promise的任意值(例如:return xx),新promise变为resolved,value为返回的值;
③.如果返回的是另一个新promise,此promise的结果就会成为新promise的结果(结果和状态跟返回的新promise的状态和结果保持一致);
- 例子
<script type="text/javascript" charset="utf-8"> new Promise((resolve,reject) => { //resolve(1); reject(2); }).then( (value) => { console.info('成功1'+value); //1 }, (reason) => { console.info('失败1'+reason); //2 //throw new Error('失败1'+reason); } ).then( (value) => { console.info('成功2'+value); //'undefined',因为前一个then()里面的回调函数成功执行了,并且回调函数的返回值为'undefined'(/没有返回值) }, (reason) => { console.info('失败2'+reason); //'失败2Error: 失败12',对应上面的[throw new Error('失败1'+reason);]代码 } ) </script>
5.5.Promise如何串连多个操作任务?
1>.promise的then()返回一个新的promise对象,可以看成then()的链式调用;
2>.通过then()的链式调用串连多个同步/异步任务;
-
例子
<script type="text/javascript" charset="utf-8"> new Promise((resolve,reject) => { setTimeout(() => { console.info('执行异步任务1'); resolve(1); }, 1000); }).then( value => { console.info('执行同步任务2',value); return 2; } ).then( value => { //返回一个新的promise对象,在里面定义异步任务 //如果直接返回一个数据(像上面的代码),那么就变成了了同步任务了 return new Promise((resolve,reject)=>{ setTimeout(() => { console.info('执行异步任务3',value); resolve(3); }, 1000); }) } ).then( value => { console.info('执行同步任务4',value); } ) </script>
-
输出结果
5.6.Promise异常传透?
1>.当使用Promise的then()链式调用时,可以在最后指定失败的回调;
2>.前面任何操作出了异常, 都会传到最后失败的回调中处理;也就是说如果前面任何操作出现了异常,并且前面没有处理,那么这个异常状态会随着这个promise一直往后面传递;
-
例子
<script type="text/javascript" charset="utf-8"> new Promise((resolve, reject) => { reject(1); }).then( value => { console.info('执行同步任务2', value); return 2 } ).then( value => { console.info('执行同步任务3', value); return 3. } ).then( value => { console.info('执行同步任务4', value); return 4. } ).catch( //由于前面的几个then()中都没有显式定义失败的回调函数(/promise中的异常操作一直都没有被处理),那么就会在then()里面默认指定[reason => {throw reason}],promise一直处于'失败'状态,然后将这个异常一直往后传递 reason => { console.info('执行同步任务4', reason); //'执行同步任务4 1' } ) </script>
-
输出结果
5.7.中断promise链?
1>.当使用promise的then()链式调用时,在中间中断,不再调用后面的回调函数;
2>.办法:在回调函数中返回一个’pendding’状态的Promise对象;
-
例子
<script type="text/javascript" charset="utf-8"> new Promise((resolve, reject) => { reject(1); }).then( value => { console.info('执行同步任务2', value); return 2 } ).then( value => { console.info('执行同步任务3', value); return 3. } ).then( value => { console.info('执行同步任务4', value); return 4. } ).catch( //由于前面的几个then()中都没有显式定义失败的回调函数(/promise中的异常操作一直都没有被处理),那么就会在then()里面默认指定[reason=>{throw reason}],然后将这个异常一直往后传递 reason => { console.info('执行同步任务4', reason); //'执行同步任务4 1' } ).then( value => { console.info('执行同步任务5', value); //由于上面catch()并没有返回值,所以默认会返回一个值为'undefined'的value; } ) </script>
-
输出结果
-
例子2
<script type="text/javascript" charset="utf-8"> new Promise((resolve, reject) => { reject(1); }).then( value => { console.info('执行同步任务2', value); return 2 } ).then( value => { console.info('执行同步任务3', value); return 3. } ).then( value => { console.info('执行同步任务4', value); return 4. } ).catch( //由于前面的几个then()中都没有显式定义失败的回调函数(/promise中的异常操作一直都没有被处理),那么就会在then()里面默认指定[reason=>{throw reason}],然后将这个异常一直往后传递 reason => { console.info('执行同步任务4', reason); //'执行同步任务4 1' //中断后续的操作 return new Promise(()=>{}); //这个新的promise状态为'pending',后面的回调函数不会执行!!! } ).then( value => { console.info('执行同步任务5', value); //由于上面catch()并没有返回值,所以默认会返回一个值为'undefined'的value; } ) </script>
-
输出结果
6.手写/自定义Promise
1>.Promise.js
/**
* 自定义的promise.js模块(基于ES5的函数自调用方式:(function(){xxx})(window))
*/
(function (window) {
//定义状态常量
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
/**
* 定义promise构造函数,参数为一个执行器函数(同步执行/加载),执行器函数的参数为两个回调函数
* @param {*} excutor
*/
function Promise(excutor) {
//将当前self对象存储起来,便于在其他地方使用
//如果是ES6的语法,那么可以在使用self的地方加上一个箭头函数'()=>{}'保证内外this所属的对象是一样的;
const self = this; //promise对象
//promise对象的status属性,存储promise对象的状态,初始值为'pending'
self.status = PENDING;
//promise对象的data属性,存储promise对象的数据,默认值为'undefined'
self.data = undefined;
//promise的callbacks属性,存储promise对象的回调函数的
self.callbacks = [];
/**
* 成功状态的回调函数
* @param {*} value
*/
function resolve(value) {
//promise对象的状态只能改变一次
//如果当前状态不是'pending',直接结束
if (self.status !== PENDING) {
return;
};
//改变promise状态为'resolved'
self.status = RESOLVED;
//存储外部的数据
self.data = value;
//如果有待执行的回调函数,立即异步执行回调回调函数
if (self.callbacks.length > 0) {
setTimeout(() => { //放入到回调队列中异步执行所有成功的回调
self.callbacks.forEach(callbacksObj => {
//异步执行
callbacksObj.OnResolved(value);
});
});
}
}
/**
* 失败状态的回调函数
* @param {*} reason
*/
function reject(reason) {
//promise对象的状态只能改变一次
//如果当前状态不是'pending',直接结束,保证多个失败promise对象只能执行第一个
if (self.status !== PENDING) {
return;
};
//改变promise的状态为'rejected'
self.status = REJECTED;
//存储外部的数据
self.data = reason;
//如果有待执行的回调函数,立即异步执行回调回调函数
if (self.callbacks.length > 0) {
setTimeout(() => { //放入到回调队列中异步执行所有失败的回调
self.callbacks.forEach(callbacksObj => {
//异步执行
callbacksObj.OnRejected(reason);
});
});
}
}
//执行器函数立即执行
try {
excutor(resolve, reject);
} catch (error) {
//如果执行器在执行过程中出现了异常,执行失败的回调函数
reject(error);
}
}
/**
* promise原型对象的then
* 用来指定成功/失败的回调函数
* 返回一个新的promise对象,根据回调函数的结果改变返回的新promise的状态
* @param {*} OnResolved
* @param {*} OnRejected
*/
Promise.prototype.then = function (OnResolved, OnRejected) {
//指定默认的成功回调,实现结果数据传透
//即在调用then()执行回调函数时,没有显式指定成功的处理操作,那么就需要默认指定[value => {return value}],promise一直处于'成功'状态,然后将这个数据结果一直往后传递
OnResolved = typeof OnResolved === 'function' ? OnResolved : value => { return value }
//指定默认的失败回调,实现错误/异常传透
//即在调用then()执行回调函数时,没有显式指定错误的处理操作;那么就需要默认指定[reason => {throw reason}],promise一直处于'失败'状态,然后将这个异常一直往后传递
//注意:不能直接[return reason],后面会直接进入到成功的处理流程!!
OnRejected = typeof OnRejected === 'function' ? OnRejected : reason => { throw reason }
//临时保存promise对象
const self = this;
//返回一个新的promise对象,promise对象的状态由里面的成功/失败的回调函数的执行状态来决定
return new Promise((resolve, reject) => {
/**
* 根据指定的回调函数的执行结果来决定返回的新promise对象的状态和结果
* @param {*} callback
*/
function handle(callback) {
/*
分析:
1).如果回调函数在执行过程中抛出了异常,那么返回的新promise就会失败,reason就是error;
2).如果回调函数返回的是非promise的任意数据,那么返回的新promise就会成功,value就是返回的非promise的数据;
3).如果回调函数返回的是promise,那么返回的新promise结果/状态就会和回调函数返回的promise的结果/状态保持一致;
*/
try {
const v = callback(self.data);
if (v instanceof Promise) {
//3).调用回调函数返回的promise的then(),根据回调函数返回的promise的结果来确定最终返回的新promise的结果
//方式一
// v.then(
// value => {
// //成功
// resolve(value);
// },
// reason => {
// //失败
// reject(reason);
// }
// )
//3).方式二(推荐使用这种方式!!)
v.then(resolve, reject);
} else {
//2).调用返回的新promise的成功回调函数
resolve(v);
}
} catch (error) {
//1).调用返回的新promise的失败回调函数
reject(error);
}
}
//如果当前promise对象的状态还是'pending',保存回调函数到callbacks数组中,等到改变状态之后再执行
//回调函数的返回值要决定返回的新promise对象的状态和结果
if (self.status === PENDING) {
self.callbacks.push({
OnResolved(value) {
//注意:这里存储在callbacks数组中两个回调函数在上面调用的时候就是异步执行的,所以这里不需要再添加setTimeout()!!!
handle(OnResolved);
},
OnRejected(reason) {
//注意:这里存储在callbacks数组中两个回调函数在上面调用的时候就是异步执行的,所以这里不需要再添加setTimeout()!!!
handle(OnRejected);
}
});
} else if (self.status === RESOLVED) {
//如果promise对象的状态为'resolved',要去异步执行成功的回调函数
setTimeout(() => {
handle(OnResolved);
});
} else if (self.status === REJECTED) {
//如果promise对象的状态为'rejected',要去异步执行失败的回调函数
setTimeout(() => {
handle(OnRejected);
});
}
});
}
/**
* promise原型对象的catch
* 用来指定失败的回调函数,返回一个新的promise对象
* @param {*} OnRejected
*/
Promise.prototype.catch = function (OnRejected) {
return this.then(undefined, OnRejected);
}
/**
* promise函数对象的resolve方法
* 返回一个指定结果的成功状态的promise
* @param {*} value
*/
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
//如果value是promise对象
if (value instanceof Promise) {
//根据value(/promise对象)的结果决定返回的新promise的结果
value.then(resolve, reject);
} else {
//如果value不是promise对象,返回一个成功状态的promise对象
resolve(value);
}
})
}
/**
* promise函数对象的reject方法
* 返回一个指定结果的失败状态的promise
* @param {*} reason
*/
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
/**
* promise函数对象的all方法
* 返回一个promise,只有当传入的所有的promise的状态都是成功,那么这个返回的promise才算成功,否则就是失败;
* @param {*} promises
*/
Promise.all = function (promises) {
//用来保存所有成功状态的promise的value
const values = new Array(promises.length);
//用来保存成功状态的promise的个数
let ResolvedCount = 0;
//返回一个新的promise对象,promise对象的状态由传入的多个promise对象的状态决定
return new Promise((resolve, reject) => {
//遍历传入的所有的promise对象
promises.forEach((promise, index) => {
//如果传入的参数中有非promise对象类型的情况,直接成功
//获取每个promise对象的结果
Promise.resolve(promise).then(
value => {
//记录成功状态的promise对象的个数
ResolvedCount++;
//成功,将成功状态的promise的value保存到values数组中
//注意:添加的顺序(要和传入的多个promise对象的顺序保存一致)
values[index] = value;
//如果传入的所有promise对象的状态都是成功,则最终返回的新promise对象的状态就是成功
if (ResolvedCount == promises.length) {
resolve(values);
}
},
reason => {
//promise对象的状态只能修改一次,即使传入的多个promise对象中有多个失败,前面的也不会被覆盖
//传入的多个promise对象中只要有一个promise对象状态为失败,返回的新promise的状态为失败
reject(reason);
}
)
})
})
}
/**
* promise函数对象的race方法
* 返回一个promise,promise的状态和结果跟传入的多个promise中第一个执行完成的promise的结果和状态保持一致
* @param {*} promises
*/
Promise.race = function (promises) {
//返回一个新的promise对象,promise对象的状态由传入的多个promise对象中第一个执行完成的promise的状态决定
//注意:传入的多个promise对象的执行顺序跟传入的数组中多个promise对象的顺序/位置没有关系
return new Promise((resolve, reject) => {
//遍历传入的所有的promise对象
promises.forEach((promise, index) => {
//如果传入的参数中有非promise对象类型的情况,直接成功
Promise.resolve(promise).then(
value => {
//第一个执行完成的promise对象的状态为成功,那么最终返回的新promise对象的状态就是成功
resolve(value);
},
reason => {
//第一个执行完成的promise对象的状态为失败,那么最终返回的新promise对象的状态就是失败
reject(reason);
}
)
})
})
}
/**
* 返回一个在指定延迟时间到达之后才确定结果的promise对象
* @param {*} value
* @param {*} delay
*/
Promise.resolveDelay = function (value, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (value instanceof Promise) {
value.then(resolve, reject);
} else {
resolve(value);
}
}, delay);
})
}
/**
* 返回一个在指定延迟时间到达之后才失败的promise对象
* @param {*} reason
* @param {*} delay
*/
Promise.rejectDelay = function (reason, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(reason);
}, delay);
})
}
//向外暴露promise函数对象
window.Promise = Promise;
})(window)
2>.index.html
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<script src="./lib/promise.js" charset="utf-8"></script>
<script charset="utf-8">
const p1 = Promise.resolve(Promise.reject(1));
const p2 = Promise.resolve(2);
const pRace = Promise.race([p2,p1]);
pRace.then(
value => {
console.log('成功:' + value);
},
reason => {
console.log('失败' + reason);
}
)
</script>
</body>
</html>
输出结果
7.async与await
MDN文档
7.1.async 函数
1>.async关键字修饰的函数的返回值为promise对象;
2>.Promise对象的结果(value/reason)由async函数执行的返回值决定;
7.2.await 表达式
1>.await关键字右侧的表达式一般为promise对象,但也可以是其它的(非promise)值;
2>.如果(右侧)表达式是promise对象,await(最终)返回的是promise成功/失败状态的返回值;
3>.如果(右侧)表达式是其它值(非promise对象的数据),直接将此值作为await(最终)的返回值;
注意
①.{await 表达式}必须写在async关键字修饰函数中,但async关键字修饰的函数中可以没有{await 表达式};
②.如果'await'关键字右侧的表达式返回的promise对象失败了,就会抛出异常,需要通过try...catch捕获处理;
- 例子
<script> // async函数的返回值是promise对象,promise对象的结果由async函数执行的返回值决定 async function fn1() { //①return 1; //成功 //throw 5; //失败 return Promise.resolve(2); //成功 } const result = fn1(); //①console.log(result); //Promise对象 result.then( value => console.log('成功', value), reason => console.log('失败', reason) ) function fn2() { //return Promise.resolve(3); return Promise.reject(4); } //'await'要放在'async'修饰的方法/函数中 async function fn3() { //正常情况下,调用fn2()函数,返回一个promise对象,如果要获取promise对象的结果,必须调用promise对象的then()函数 //但是现在可以在调用的返回promise对象的函数的左边添加一个'await'关键字,意思就是说,等到(函数fn2里面返回的)promise对象(/表达式)执行完成,最终获取到(表达式返回的)promise对象的成功/失败的返回值 //const value = await fn2(); //返回promise对象的处理(成功/失败)结果 //const value = await 5; //'await'关键字的右边也可以是一个非promise对象的数据,那么最终获取到这个'非promise对象的数据(/表达式)'本身(的值) //console.log('成功',value); //3 //如果要得到失败状态的promise的返回值,必须要加上try...catch try { const value = await fn2(); console.log('成功3',value); } catch (error) { console.log('失败3',error); //4 } } //调用fn3()函数 fn3(); </script>
8.JS异步之宏队列与微队列
8.1.原理图
单线程执行!!!
8.2.说明
1>.JS中用来存储待执行的回调函数的队列包含2个不同特点的列队;
2>.宏列队:用来保存待执行的宏任务(回调),比如:定时器回调/DOM事件回调/ajax回调;
3>.微列队:用来保存待执行的微任务(回调),比如:promise的回调/MutationObserver的回调;
4>.JS执行时会区别这2个队列:
①.JS引擎首先必须先执行所有的初始化同步任务代码;
②.之后每次准备取出第一个宏任务执行之前,都要先将所有的微任务一个一个取出来执行;
- 也就是说微队列里面的微任务优先级高于宏队列里面的宏任务,即每次执行宏任务之前都要等到所有的微任务都执行完成,然后才会执行宏任务;
- 同类型的队列(宏队列/微队列)中的任务是按照任务添加到队列中的先后顺序去执行的;
③.如果宏任务中包含了一个微任务,那么当宏任务执行完成之后立刻执行内部的微任务;
-
例子
<script> //console.log('同步代码'); //必须先执行所有的初始化同步任务代码,然后才可以执行队列中的回调函数(任务) setTimeout(() => { //这个异步任务会立即放入到宏队列中 console.log('timeout callback'); //③ //这个微任务会在前面的宏任务执行完成之后立刻执行 //这个微任务会在后面的宏任务执行之前立刻执行,因为在后面的宏任务执行之前要先将所有的微任务一个一个取出来执行 Promise.resolve(3).then( value => console.log('promise onResolve3', value) //④ ) }, 0); setTimeout(() => { //这个异步任务会立即放入到宏队列中 console.log('timeout callback2'); //⑤.最后执行 }, 0); Promise.resolve(1).then( value => { //这个异步任务会立即放入到微队列中 console.log('promise onResolve', value); //①.先执行 } ); Promise.resolve(2).then( value => { //这个异步任务会立即放入到微队列中 console.log('promise onResolve2', value); //② } ); </script>
-
输出结果
附:Promise相关面试题:分析代码执行顺序
<script>
setTimeout(() => {
console.log("0") //宏任务,最后执行;<9>
}, 0)
new Promise((resolve, reject) => { //对象初始化,同步代码
console.log("1") //同步代码,最先执行;<1>
resolve() //返回一个成功状态的promise的promise对象
}).then(() => { //回调函数,微任务
console.log("2") //微任务;<3>
new Promise((resolve, reject) => { //对象初始化,同步代码,要在前面的'console.log("2")'执行完成之后才会执行
console.log("3") //同步代码;<4>
resolve() //返回一个成功状态的promise对象
}).then(() => { //回调函数,微任务
console.log("4") //微任务;<6>
}).then(() => { //回调函数,微任务;在上面的then()执行完成之前临时放到一个callback[]中,等到上面的then()执行完成返回一个promise对象之后才会将微任务放入到微队列中
console.log("5") //微任务;<8>
})
}).then(() => { //回调函数,微任务;在上面的then()执行完成之前临时放到一个callback[]中,等到前面的then()执行完成返回一个promise对象之后才会将微任务放入到微队列中
console.log("6") //微任务;<7>
})
new Promise((resolve, reject) => {
console.log("7") //同步代码;<2>
resolve() //返回一个成功状态的promise对象
}).then(() => { //回调函数,微任务
console.log("8") //微任务;<5>
})
</script>