Promise - 图1

概述

  1. 如果直接使用传统回调方式去完成复杂的异步流程,就无法避免大量的回调函数嵌套(回调地狱)
  2. Promise 就是一个对象用来去表示一个异步任务结束后最终是成功(Fulfilled)还是失败(Rejected)在状态明确过后都会有相对于的状态 成功(onFulfilled) 失败(onRejected)也会有相对于的任务会被执行且一旦明确结果就不可能发生改变了
  3. CommonJS社区提出了Promise的规范
    1. 为异步编程提供一种更合理更强大的统一解决方案
    2. 后来再ES2015中被标准化,成为语言规范

      基本用法

      注意事项:
      即便promise里没有任何异步操作 then方法指定的回调函数仍然会去消息队列排队,必须要等待同步代码执行完毕后才执行
      示例: ```javascript // promise 基本示例

const promise = new Promise(function (resolve, reject) { // 这里‘兑现’承诺 // 一旦明确结果就不可能发生改变了 所以只能有一种状态 resolve(100) // 承诺达成

  1. // reject(new Error('promise rejected')) // 承诺失败

})

// 即便promise里没有任何异步操作 then方法指定的回调函数仍然会去消息队列排队 // 必须要等待同步代码执行完毕后才执行 promise.then( function onFulfilled (value) { console.log(‘成功回调’, value); }, function onRejected (error) { console.log(‘失败回调’, error); } ) console.log(‘End’);

  1. <a name="aln4P"></a>
  2. ## 使用案例 (Promise 方式的 AJAX)
  3. ```javascript
  4. // Promise 方式的 AJAX
  5. function ajax(url) {
  6. return new Promise(function(resolve,reject){
  7. var xhr = new XMLHttpRequest()
  8. xhr.open('GET',url)
  9. // HTML5中引入的新特性 请求完成后直接拿到 json对象
  10. xhr.responseType = 'json';
  11. // HTML5中引入的新特性 请求完成后执行
  12. xhr.onload = function(){
  13. if(this.status === 200){
  14. resolve(this.response)
  15. }else {
  16. reject(new Error(this.statusText))
  17. }
  18. }
  19. xhr.send();
  20. })
  21. }
  22. ajax('./api/users.json').then(function(res){
  23. console.log(res);
  24. },function(err){
  25. console.log("错误",err);
  26. })

问题:
本地使用可能会加载不了本地的文件
解决方案:
安装 live-server
安装:npm install -g live-server
运行:live-server
url地址会被改变,不再是file协议的地址

常见误区

  1. Promise本质上也是使用回调函数的方式去定义异步任务 结束后所需要执行的任务只是这里的回调函数是通过then方法传递进去的
  2. 既然还是回调函数 那么我们串联执行多个任务还是会有回调函数嵌套问题
  3. 示例: ```javascript // promise 误区 —- 嵌套的使用方式是使用Promise最常见的误区 function ajax(url) { return new Promise(function(resolve,reject){
    1. var xhr = new XMLHttpRequest()
    2. xhr.open('GET',url)
    3. // HTML5中引入的新特性 请求完成后直接拿到 json对象
    4. xhr.responseType = 'json';
    5. // HTML5中引入的新特性 请求完成后执行
    6. xhr.onload = function(){
    7. if(this.status === 200){
    8. resolve(this.response)
    9. }else {
    10. reject(new Error(this.statusText))
    11. }
    12. }
    13. xhr.send();
    }) }

// 使用传统的思考方式做的话 ajax(‘/api/urls.json’).then(function(urls){ ajax(urls.users).then(function(urls){ ajax(urls.users).then(function(urls){ // 仍会形成回调地狱 }) }) })

  1. 嵌套的使用方式是使用Promise最常见的误区<br />实际上的做法应该是借助Promise then方法链式调用的特点 尽量保证异步任务的扁平化
  2. <a name="o5CPF"></a>
  3. ## 链式调用
  4. 示例:
  5. ```javascript
  6. // Promise 链式调用
  7. function ajax(url) {
  8. return new Promise(function (resolve, reject) {
  9. var xhr = new XMLHttpRequest()
  10. xhr.open('GET', url)
  11. // HTML5中引入的新特性 请求完成后直接拿到 json对象
  12. xhr.responseType = 'json';
  13. // HTML5中引入的新特性 请求完成后执行
  14. xhr.onload = function () {
  15. if (this.status === 200) {
  16. resolve(this.response)
  17. } else {
  18. reject(new Error(this.statusText))
  19. }
  20. }
  21. xhr.send();
  22. })
  23. }
  24. var prrmise = ajax('./api/users.json')
  25. var promise2 = prrmise.then(
  26. function onFulfilled (res) {
  27. console.log(res);
  28. },
  29. function onRejected (err) {
  30. console.log("错误", err);
  31. }
  32. )
  33. console.log(promise2 === prrmise);
  34. ajax('./api/users.json')
  35. // then方法返回一个全新的 promise对象
  36. // 实现promise链条
  37. // 每一个then方法都是在为上一个then返回的promise对象添加状态明确过后的回调
  38. // 依次执行
  39. // 可以手动返回一个promise 对象
  40. // 可以避免不必要的回调嵌套 保证代码的扁平化
  41. // 如果回调返回的是promise 那么后面的then方法的回调会等待他结束
  42. .then(function(value){
  43. console.log('111');
  44. return ajax('./api/users.json')
  45. }) // => promise
  46. .then(function(value){
  47. console.log('222');
  48. console.log(value);
  49. }) // => promise
  50. .then(function(value){
  51. console.log('333');
  52. // 前面then的返回值会作为后面then方法的回调参数
  53. // 如果没有返回任何值 那么默认返回undefind
  54. return 'foo'
  55. }) // => promise
  56. .then(function(value){
  57. console.log('444');
  58. console.log(value);
  59. })

总结:

  1. then方法返回一个全新的 promise对象
  2. 每一个then方法都是在为上一个then返回的promise对象添加状态明确过后的回调
  3. then方法依次执行
  4. 可以手动返回一个promise 对象
  5. 可以避免不必要的回调嵌套 保证代码的扁平化
  6. 如果回调返回的是promise 那么后面的then方法的回调会等待他结束
  7. 前面then的返回值会作为后面then方法的回调参数
  8. 如果没有返回任何值 那么默认返回undefind

    异常处理

    示例:

    1. // Promise catch
    2. // Promise 方式的 AJAX
    3. function ajax(url) {
    4. return new Promise(function (resolve, reject) {
    5. // foo()
    6. // throw new Error()
    7. var xhr = new XMLHttpRequest()
    8. xhr.open('GET', url)
    9. // HTML5中引入的新特性 请求完成后直接拿到 json对象
    10. xhr.responseType = 'json';
    11. // HTML5中引入的新特性 请求完成后执行
    12. xhr.onload = function () {
    13. if (this.status === 200) {
    14. resolve(this.response)
    15. } else {
    16. reject(new Error(this.statusText))
    17. }
    18. }
    19. xhr.send();
    20. })
    21. }
    22. // 错误 ---》 调用onRejected
    23. // ajax('./api/users.json')
    24. // .then(
    25. // function onFulfilled(value) {
    26. // console.log('成功回调', value);
    27. // }, function onRejected(error) {
    28. // console.log('失败回调', error);
    29. // }
    30. // )
    31. // 失败 -----》 使用catch
    32. // ajax('./api/users11.json')
    33. // .then(
    34. // function onFulfilled(value) {
    35. // console.log('成功回调', value);
    36. // }
    37. // )
    38. // .catch(function onRejected(error) {
    39. // console.log('失败回调2', error);
    40. // })
    41. // 这种方法的捕获异常 只是给当前的promise对象捕获
    42. ajax('./api/users.json')
    43. .then(
    44. function onFulfilled(value) {
    45. console.log('成功回调', value);
    46. // 这里的异常并没有被捕获
    47. return ajax('/err')
    48. }, function onRejected(error) {
    49. console.log('失败回调', error);
    50. }
    51. )
    52. // 因为是链条 所以前面promise的错误会一直往后传递 直到被捕获
    53. // 所以后面的catch 才能捕获第一个的异常
    54. // 在代码中应该明确捕获每一个异常 而不是给全局统一处理
    55. ajax('./api/users.json')
    56. .then(
    57. function onFulfilled(value) {
    58. console.log('成功回调', value);
    59. return ajax('/err')
    60. }
    61. )
    62. .catch(function onRejected(error) {
    63. console.log('失败回调2', error);
    64. })

    总结:

  9. 在代码中应该明确捕获每一个异常 而不是给全局统一处理

  10. 用正常方式捕获的异常 then(成功,失败)
    1. 这种方法的捕获异常 只是给当前的promise对象捕获
  11. 使用链式调用 then(成功).catch(失败)
    1. 因为是链条 所以前面promise的错误会一直往后传递 直到被捕获

      静态方法

      示例: ```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(); }) }

// 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); })

  1. resolve
  2. 1. 快速的把一个值转换为一个promise对象 直接返回一个状态为Fulfilled(成功)的promise对象
  3. 1. 参数会作为返回值
  4. 1. 如果传入的是promise 对象 那么原样返回这个promise对象
  5. 1. 特殊情况:
  6. 1. 如果传入的是对象 而且这个对象也有一个跟promise 一样的then方法 那么这样的对象也可以作为promoise被执行 这种带有then方法的对象 可以说是实现了一个thenable的接口 也就是可以被then的对象
  7. reject:<br />创建一个一定失败的promise对象
  8. <a name="0TWRp"></a>
  9. ## 并行执行
  10. Promise.all()
  11. 1. 把多个promise 合并成一个
  12. 1. 会返回一个全新的promise对象
  13. 1. 当内部所有的promise 都执行完 返回的这个全新的promise 才会完成
  14. 1. 拿到的结果是一个数组包 含着每个异步任务执行的结果
  15. 1. 只有都成功结束 这个新的promise 才会结束
  16. 1. 如果其中有任何一个任务失败 就会以失败结束
  17. 1. 允许按异步代码调用的顺序得到异步代码执行的结果
  18. promise.race()<br />race 以第一个结束的promise 为准<br />区别:<br />promise.all() 是等待所有任务结束<br />promise.race() 只要有一个任务结束 就结束<br />示例:
  19. ```javascript
  20. // 并行执行
  21. function ajax(url) {
  22. return new Promise(function (resolve, reject) {
  23. var xhr = new XMLHttpRequest()
  24. xhr.open('GET', url)
  25. // HTML5中引入的新特性 请求完成后直接拿到 json对象
  26. xhr.responseType = 'json';
  27. // HTML5中引入的新特性 请求完成后执行
  28. xhr.onload = function () {
  29. if (this.status === 200) {
  30. resolve(this.response)
  31. } else {
  32. reject(new Error(this.statusText))
  33. }
  34. }
  35. xhr.send();
  36. })
  37. }
  38. // ---- all() -----
  39. // ajax('./api/users.json')
  40. // ajax('./api/posts.json')
  41. // 把多个promise 合并成一个
  42. // 会返回一个全新的promise对象
  43. // 当内部所有的promise 都执行完 返回的这个全新的promise 才会完成
  44. // let promise = Promise.all([
  45. // ajax('./api/users.json'),
  46. // ajax('./api/posts.json')
  47. // ])
  48. // 拿到的结果是一个数组
  49. // 包含着每个异步任务执行的结果
  50. // 只有都成功结束 这个新的promise 才会结束
  51. // 如果其中有任何一个任务失败 就会以失败结束
  52. // promise.then(function(values){
  53. // console.log(values);
  54. // }).catch(function(err){
  55. // console.log(err);
  56. // })
  57. ajax('./api/urls.json')
  58. .then(value => {
  59. // Object.values 获取所有属性的值组成的数组
  60. const urls = Object.values(value)
  61. console.log(urls);
  62. // 将字符串数组转换成包含所有请求任务的promise 数组
  63. const tasks = urls.map(url => ajax(url))
  64. console.log(tasks);
  65. return Promise.all(tasks)
  66. }).then(values => {
  67. console.log(values);
  68. })
  69. // --- race() ---
  70. // promise.all() 是等待所有任务结束
  71. // promise.race() 只要有一个任务结束 就结束
  72. // race 以第一个结束的promise 为准
  73. // 这里是 如果 这个ajax请求在五百毫秒内完成 那么就正常返回
  74. // 五百毫秒后 timeout 会以失败的方式结束 而race以第一个结束的promise 为准
  75. // 可用于实现ajax请求超时控制
  76. const request = ajax('/api/posts.json')
  77. const timeout = new Promise((resolve, reject) => {
  78. setTimeout(() => reject(new Error('timeout')), 500);
  79. })
  80. Promise.race([
  81. request,
  82. timeout
  83. ])
  84. .then(value => {
  85. console.log(value);
  86. })
  87. .catch(error => {
  88. console.log(error)
  89. })

执行时序(宏任务/微任务)

宏任务

回调队列中的任务称之为宏任务
宏任务执行的过程中可以临时加上一些额外的需求
可以选择作为一个新的宏任务重新进入队列中排队
也可以作为当前任务的微任务直接在当前任务结束后立即执行
目前绝大数的异步调用 都会作为宏任务执行

微任务

为了提高整体的响应能力
直接在当前任务结束后立即执行
promise/ MutationObserver/ node中的 process.nextTick 都是微任务
image.png

  1. // promise执行时序 宏任务 微任务
  2. // 微任务
  3. console.log('start');
  4. setTimeout(() => {
  5. console.log('settimeout');
  6. }, 0);
  7. // 没有异步操作也会异步调用
  8. Promise.resolve()
  9. .then(() => {
  10. console.log('promise1');
  11. })
  12. .then(() => {
  13. console.log('promise2');
  14. })
  15. .then(() => {
  16. console.log('promise3');
  17. })
  18. console.log('end');
  19. // 回调队列中的任务称之为宏任务
  20. // 宏任务执行的过程中可以临时加上一些额外的需求
  21. // 可以选择作为一个新的宏任务重新进入队列中排队
  22. // 也可以作为当前任务的微任务
  23. // 直接在当前任务结束后立即执行
  24. // promise 作为微任务 他会在本轮结束的末尾作为微任务执行
  25. // 微任务 为了提高整体的响应能力
  26. // 目前绝大数的异步调用 都会作为宏任务执行
  27. // promise MutationObserver node中的 process.nextTick 都是微任务