概述
- 如果直接使用传统回调方式去完成复杂的异步流程,就无法避免大量的回调函数嵌套(回调地狱)
- Promise 就是一个对象用来去表示一个异步任务结束后最终是成功(Fulfilled)还是失败(Rejected)在状态明确过后都会有相对于的状态 成功(onFulfilled) 失败(onRejected)也会有相对于的任务会被执行且一旦明确结果就不可能发生改变了
- CommonJS社区提出了Promise的规范
const promise = new Promise(function (resolve, reject) { // 这里‘兑现’承诺 // 一旦明确结果就不可能发生改变了 所以只能有一种状态 resolve(100) // 承诺达成
// reject(new Error('promise rejected')) // 承诺失败
})
// 即便promise里没有任何异步操作 then方法指定的回调函数仍然会去消息队列排队 // 必须要等待同步代码执行完毕后才执行 promise.then( function onFulfilled (value) { console.log(‘成功回调’, value); }, function onRejected (error) { console.log(‘失败回调’, error); } ) console.log(‘End’);
<a name="aln4P"></a>## 使用案例 (Promise 方式的 AJAX)```javascript// Promise 方式的 AJAXfunction ajax(url) {return new Promise(function(resolve,reject){var xhr = new XMLHttpRequest()xhr.open('GET',url)// HTML5中引入的新特性 请求完成后直接拿到 json对象xhr.responseType = 'json';// HTML5中引入的新特性 请求完成后执行xhr.onload = function(){if(this.status === 200){resolve(this.response)}else {reject(new Error(this.statusText))}}xhr.send();})}ajax('./api/users.json').then(function(res){console.log(res);},function(err){console.log("错误",err);})
问题:
本地使用可能会加载不了本地的文件
解决方案:
安装 live-server
安装:npm install -g live-server
运行:live-server
url地址会被改变,不再是file协议的地址
常见误区
- Promise本质上也是使用回调函数的方式去定义异步任务 结束后所需要执行的任务只是这里的回调函数是通过then方法传递进去的
- 既然还是回调函数 那么我们串联执行多个任务还是会有回调函数嵌套问题
- 示例:
```javascript
// promise 误区 —- 嵌套的使用方式是使用Promise最常见的误区
function ajax(url) {
return new Promise(function(resolve,reject){
}) }var xhr = new XMLHttpRequest()xhr.open('GET',url)// HTML5中引入的新特性 请求完成后直接拿到 json对象xhr.responseType = 'json';// HTML5中引入的新特性 请求完成后执行xhr.onload = function(){if(this.status === 200){resolve(this.response)}else {reject(new Error(this.statusText))}}xhr.send();
// 使用传统的思考方式做的话 ajax(‘/api/urls.json’).then(function(urls){ ajax(urls.users).then(function(urls){ ajax(urls.users).then(function(urls){ // 仍会形成回调地狱 }) }) })
嵌套的使用方式是使用Promise最常见的误区<br />实际上的做法应该是借助Promise then方法链式调用的特点 尽量保证异步任务的扁平化<a name="o5CPF"></a>## 链式调用示例:```javascript// Promise 链式调用function ajax(url) {return new Promise(function (resolve, reject) {var xhr = new XMLHttpRequest()xhr.open('GET', url)// HTML5中引入的新特性 请求完成后直接拿到 json对象xhr.responseType = 'json';// HTML5中引入的新特性 请求完成后执行xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}var prrmise = ajax('./api/users.json')var promise2 = prrmise.then(function onFulfilled (res) {console.log(res);},function onRejected (err) {console.log("错误", err);})console.log(promise2 === prrmise);ajax('./api/users.json')// then方法返回一个全新的 promise对象// 实现promise链条// 每一个then方法都是在为上一个then返回的promise对象添加状态明确过后的回调// 依次执行// 可以手动返回一个promise 对象// 可以避免不必要的回调嵌套 保证代码的扁平化// 如果回调返回的是promise 那么后面的then方法的回调会等待他结束.then(function(value){console.log('111');return ajax('./api/users.json')}) // => promise.then(function(value){console.log('222');console.log(value);}) // => promise.then(function(value){console.log('333');// 前面then的返回值会作为后面then方法的回调参数// 如果没有返回任何值 那么默认返回undefindreturn 'foo'}) // => promise.then(function(value){console.log('444');console.log(value);})
总结:
- then方法返回一个全新的 promise对象
- 每一个then方法都是在为上一个then返回的promise对象添加状态明确过后的回调
- then方法依次执行
- 可以手动返回一个promise 对象
- 可以避免不必要的回调嵌套 保证代码的扁平化
- 如果回调返回的是promise 那么后面的then方法的回调会等待他结束
- 前面then的返回值会作为后面then方法的回调参数
-
异常处理
示例:
// Promise catch// Promise 方式的 AJAXfunction ajax(url) {return new Promise(function (resolve, reject) {// foo()// throw new Error()var xhr = new XMLHttpRequest()xhr.open('GET', url)// HTML5中引入的新特性 请求完成后直接拿到 json对象xhr.responseType = 'json';// HTML5中引入的新特性 请求完成后执行xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}// 错误 ---》 调用onRejected// ajax('./api/users.json')// .then(// function onFulfilled(value) {// console.log('成功回调', value);// }, function onRejected(error) {// console.log('失败回调', error);// }// )// 失败 -----》 使用catch// ajax('./api/users11.json')// .then(// function onFulfilled(value) {// console.log('成功回调', value);// }// )// .catch(function onRejected(error) {// console.log('失败回调2', error);// })// 这种方法的捕获异常 只是给当前的promise对象捕获ajax('./api/users.json').then(function onFulfilled(value) {console.log('成功回调', value);// 这里的异常并没有被捕获return ajax('/err')}, function onRejected(error) {console.log('失败回调', error);})// 因为是链条 所以前面promise的错误会一直往后传递 直到被捕获// 所以后面的catch 才能捕获第一个的异常// 在代码中应该明确捕获每一个异常 而不是给全局统一处理ajax('./api/users.json').then(function onFulfilled(value) {console.log('成功回调', value);return ajax('/err')}).catch(function onRejected(error) {console.log('失败回调2', error);})
总结:
在代码中应该明确捕获每一个异常 而不是给全局统一处理
- 用正常方式捕获的异常 then(成功,失败)
- 这种方法的捕获异常 只是给当前的promise对象捕获
- 使用链式调用 then(成功).catch(失败)
function ajax(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest() xhr.open(‘GET’, url) // HTML5中引入的新特性 请求完成后直接拿到 json对象 xhr.responseType = ‘json’; // HTML5中引入的新特性 请求完成后执行 xhr.onload = function () { if (this.status === 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } } xhr.send(); }) }
// resolve() 快速的把一个值转换为一个一定成功的promise对象 // 直接返回一个状态为Fulfilled(成功)的promise对象 // 参数会作为返回值 Promise.resolve(‘foo’) .then(function (value) { console.log(value); }) // 等价于 new Promise(function (resolve, reject) { resolve(‘foo’) }) // 如果传入的是promise 对象 那么原样返回这个promise对象 let promise1 = ajax(‘./api/users.json’) let promise2 = Promise.resolve(promise1); console.log(promise1 === promise2); // 特殊情况 如果传入的是对象 而且这个对象也有一个跟promise 一样的then方法 // 那么这样的对象也可以作为promoise被执行 // 这种带有then方法的对象 可以说是实现了一个thenable的接口 // 也就是可以被then的对象 Promise.resolve({ then: function (resolve, reject) { resolve(‘foo’) } }).then(function (value) { console.log(value); })
// reject 方法 创建一个一定失败的promise对象 Promise.reject(new Error(‘reject’)) .catch(function (err) { console.log(err); }) Promise.reject(‘errr’) .catch(function (err) { console.log(err); })
resolve:1. 快速的把一个值转换为一个promise对象 直接返回一个状态为Fulfilled(成功)的promise对象1. 参数会作为返回值1. 如果传入的是promise 对象 那么原样返回这个promise对象1. 特殊情况:1. 如果传入的是对象 而且这个对象也有一个跟promise 一样的then方法 那么这样的对象也可以作为promoise被执行 这种带有then方法的对象 可以说是实现了一个thenable的接口 也就是可以被then的对象reject:<br />创建一个一定失败的promise对象<a name="0TWRp"></a>## 并行执行Promise.all()1. 把多个promise 合并成一个1. 会返回一个全新的promise对象1. 当内部所有的promise 都执行完 返回的这个全新的promise 才会完成1. 拿到的结果是一个数组包 含着每个异步任务执行的结果1. 只有都成功结束 这个新的promise 才会结束1. 如果其中有任何一个任务失败 就会以失败结束1. 允许按异步代码调用的顺序得到异步代码执行的结果promise.race()<br />race 以第一个结束的promise 为准<br />区别:<br />promise.all() 是等待所有任务结束<br />promise.race() 只要有一个任务结束 就结束<br />示例:```javascript// 并行执行function ajax(url) {return new Promise(function (resolve, reject) {var xhr = new XMLHttpRequest()xhr.open('GET', url)// HTML5中引入的新特性 请求完成后直接拿到 json对象xhr.responseType = 'json';// HTML5中引入的新特性 请求完成后执行xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}// ---- all() -----// ajax('./api/users.json')// ajax('./api/posts.json')// 把多个promise 合并成一个// 会返回一个全新的promise对象// 当内部所有的promise 都执行完 返回的这个全新的promise 才会完成// let promise = Promise.all([// ajax('./api/users.json'),// ajax('./api/posts.json')// ])// 拿到的结果是一个数组// 包含着每个异步任务执行的结果// 只有都成功结束 这个新的promise 才会结束// 如果其中有任何一个任务失败 就会以失败结束// promise.then(function(values){// console.log(values);// }).catch(function(err){// console.log(err);// })ajax('./api/urls.json').then(value => {// Object.values 获取所有属性的值组成的数组const urls = Object.values(value)console.log(urls);// 将字符串数组转换成包含所有请求任务的promise 数组const tasks = urls.map(url => ajax(url))console.log(tasks);return Promise.all(tasks)}).then(values => {console.log(values);})// --- race() ---// promise.all() 是等待所有任务结束// promise.race() 只要有一个任务结束 就结束// race 以第一个结束的promise 为准// 这里是 如果 这个ajax请求在五百毫秒内完成 那么就正常返回// 五百毫秒后 timeout 会以失败的方式结束 而race以第一个结束的promise 为准// 可用于实现ajax请求超时控制const request = ajax('/api/posts.json')const timeout = new Promise((resolve, reject) => {setTimeout(() => reject(new Error('timeout')), 500);})Promise.race([request,timeout]).then(value => {console.log(value);}).catch(error => {console.log(error)})
执行时序(宏任务/微任务)
宏任务
回调队列中的任务称之为宏任务
宏任务执行的过程中可以临时加上一些额外的需求
可以选择作为一个新的宏任务重新进入队列中排队
也可以作为当前任务的微任务直接在当前任务结束后立即执行
目前绝大数的异步调用 都会作为宏任务执行
微任务
为了提高整体的响应能力
直接在当前任务结束后立即执行
promise/ MutationObserver/ node中的 process.nextTick 都是微任务
// promise执行时序 宏任务 微任务// 微任务console.log('start');setTimeout(() => {console.log('settimeout');}, 0);// 没有异步操作也会异步调用Promise.resolve().then(() => {console.log('promise1');}).then(() => {console.log('promise2');}).then(() => {console.log('promise3');})console.log('end');// 回调队列中的任务称之为宏任务// 宏任务执行的过程中可以临时加上一些额外的需求// 可以选择作为一个新的宏任务重新进入队列中排队// 也可以作为当前任务的微任务// 直接在当前任务结束后立即执行// promise 作为微任务 他会在本轮结束的末尾作为微任务执行// 微任务 为了提高整体的响应能力// 目前绝大数的异步调用 都会作为宏任务执行// promise MutationObserver node中的 process.nextTick 都是微任务

