Promise async await

简介:回调

  • JS会提供很多函数,允许异步行为。换句话说,现在开始执行的行为。但它们会在稍后完成。
  • 异步执行某项功能的函数应该提供一个 callback 参数用于在相应事件完成时调用。
  • 处理Error
    • 加载成功时,它会调用 callback(null, xxx),否则调用 callback(error),这种被称为Error优先回调风格。
    • 约定:
      • callback 的第一个参数是为 error 而保留的。一旦出现 error,callback(err) 就会被调用。
      • 第二个参数(和下一个参数,如果需要的话)用于成功的结果,此时 callback(null, result1, result2…) 就会被调用。
    • 单一的 callback 函数可以同时具有报告 error 和传递返回结果的作用。
  • 但对于一个接一个的多个异步行为,随着调用嵌套的增加,代码层次变得更深,维护难度也随之增加,尤其是我们使用的是可能包含了很多循环和条件语句的真实代码,这些被称为“回调地狱”或“厄运金字塔”。
  • 有其他的方法可以避免此类金字塔,最好的方法之一就是promise

Promise

  • Promise是将生产者代码和消费者代码连接在一起的一个特殊JS对象,是异步编程的一种解决方案

  • 构造器语法:

    let promise = new Promise(function(resolve, reject) {
      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    
  • 传递给 new Promise 的函数被称为 executor,当 new Promise 被创建,executor 会自动运行

  • 它的参数 resolvereject 是由JS自身提供的回调,代码仅在 executor 的内部。当 executor 获得了结果,无论是早还是晚都没关系,它应该调用以下回调之一:

    • resolve(value):任务成功完成并带有结果value
    • reject(error):如果出现了errorerror即为error对象
  • executor 只能调用一个 resolve一个 reject,任何状态的更改都是最终的。并且,resolve/reject 只需要一个参数(或不包含任何参数)

  • executor 通常是异步执行某些操作,并在一段时间后调用 resolve/reject,但这不是必须的,我们可以立即调用 resolvereject

  • promise 对象具有以下内部属性

    • state :最初是 "pending"
      • resolve 被调用时变为 "fulfilled"
      • reject 被调用时变为 "rejected"
    • result:最初是 undefined
      • resolve(value) 被调用时变为 value
      • reject(error) 被调用时变为 error
    • 与最初的 pending promise 相反,一个 resolvedrejectedpromise 都会被称为 settled
      在这里插入图片描述
  • stateresult 属性都是内部的,无法直接访问它们,但我们可以对它们使用 .then/.catch/.finally 方法

  • 消费者:then, catch

    • .then语法:

      promise.then(
        function(result) { /* handle a successful result */ },
        function(error) { /* handle an error */ }
      );
      
      • .then 的第一个参数是一个函数,在 promise resolved 且接收到结果后执行
      • .then 的第二个参数也是一个函数,在 promise rejected 且接收到 error 信息后执行
    • 只对成功完成的情况感兴趣,可以只为 .then 提供一个函数参数

    • 只对 error 感兴趣,可以使用 null 作为第一个参数:.then(null, f),或者也可以使用 .catch(f)

  • 简单的例子:

function timeout(ms) {
   return new Promise((resolve, reject) => {
     setTimeout(resolve, ms, 'done');
   });
 }
 
 timeout(100).then((value) => {
 console.log(value);
 });
  • timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

  • promise 中也有 finally,调用 .finally(fn) 类似于 .then(f, f),因为当 promise settled 时 f 就会执行:无论 promise 被 resolve 还是 reject。finally 的功能是设置一个处理程序在前面的操作完成后,执行清理/终结。

  • PS:finally(f) 并不完全是 then(f, f) 的别名

    • finally 没有参数
    • finally 将结果或 error “传递”给下一个合适的处理程序
      • 成功就传递给下一个then
      • 失败就传递给下一个catch
    • finally 也不应该返回任何内容,如果它返回了会默认被忽略
  • 基于回调的好处:

    • promise允许我们按照自然顺序进行编码;而callback就必须要知道如何处理结果
    • promise可以多次调用.then;而callback只能有一个回调
  • 常见写法:

    new Promise(请求1)
        .then(请求2(请求结果1))
        .then(请求3(请求结果2))
        .then(请求4(请求结果3))
        .then(请求5(请求结果4))
        .catch(处理异常(异常信息))
    

使用promise进行错误处理

  • 当一个promisereject时,将移交到最近的rejection处理程序

  • .catch 处理 promise 中的各种 error:在 reject() 调用中的,或者在处理程序中抛出的 error

  • 如果给定 .then 的第二个参数(即 error 处理程序),那么 .then 也会以相同的方式捕获 error

  • 未处理的rejection

    new Promise(function() {
      noSuchFunction(); // 这里出现 error(没有这个函数)
    })
      .then(() => {
        // 一个或多个成功的 promise 处理程序
      }); // 尾端没有 .catch!
    
    • 如果出现了一个 error,并且在这没有 .catch,那么 unhandledrejection 处理程序就会被触发,并获取具有 error 相关信息的 event 对象,所以我们就能做一些后续处理了

      window.addEventListener('unhandledrejection', function(event) {
        // 这个事件对象有两个特殊的属性:
        alert(event.promise); // [object Promise] —— 生成该全局 error 的 promise
        alert(event.reason); // Error: Whoops! —— 未处理的 error 对象
      });
      
      new Promise(function() {
        throw new Error("Whoops!");
      }); // 没有用来处理 error 的 catch
      
  • 例子 .catch会被触发吗:

    new Promise(function(resolve, reject) {
      setTimeout(() => {
        throw new Error("Whoops!");
      }, 1000);
    }).catch(alert);
    
    • 不一定会被触发。函数代码周围有个隐式的 try..catch,所有同步错误都会得到处理。但是这里的错误并不是在 executor 运行时生成的,而是在稍后生成的,因此,promise 无法处理它。

Promise API

Promise.all

  • 并行执行多个 promise,并等待所有 promise 都准备就绪

  • 使用场景:一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化

  • Promise.all 接受一个可迭代对象(通常是一个数组项为 promise 的数组),并返回一个新的 promise

  • 例子:

    Promise.all([
      new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
      new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
      new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
    ]).then(alert); // 1,2,3 当上面这些 promise 准备好时:每个 promise 都贡献了数组中的一个元素
    
    • 结果数组中元素的顺序与其在源 promise 中的顺序相同。即使第一个 promise 花费了最长的时间才 resolve,但它仍是结果数组中的第一个
  • 任意一个 promise 被 reject,由 Promise.all 返回的 promise 就会立即 reject,并且带有的就是这个 error

  • Promise.all(...) 接受含有 promise 项的可迭代对象(大多数情况下是数组)作为参数,但如果这些对象中的任何一个不是 promise,那么它将被按原样传递给结果数组

    Promise.all([
      new Promise((resolve, reject) => {
        setTimeout(() => resolve(1), 1000)
      }),
      2,
      3
    ]).then(alert); // 1, 2, 3
    

Promise.allSettled

  • 最近新增的特性,旧浏览器可能需要polyfills
  • Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何,结果数组具有:
    • {status:"fulfilled", value:result} 对于成功的响应
    • {status:"rejected", reason:error} 对于 error
  • 例如,我们想要获取(fetch)多个用户的信息。即使其中一个请求失败,我们仍然对其他的感兴趣

Promise.race

  • 只等待第一个settledpromise并获取其结果或error

  • 例子:

    let promise = Promise.race(iterable);
    Promise.race([
      new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
      new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
      new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
    ]).then(alert); // 1
    
    • 第一个 promise 最快,所以它变成了结果,其他的result/error会被忽略
  • 谁跑得快,以谁为准执行回调

  • 使用场景:可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作

     //请求某个图片资源
        function requestImg(){
            var p = new Promise((resolve, reject) => {
                var img = new Image();
                img.onload = function(){
                    resolve(img);
                }
                img.src = '图片的路径';
            });
            return p;
        }
        //延时函数,用于给请求计时
        function timeout(){
            var p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject('图片请求超时');
                }, 5000);
            });
            return p;
        }
        Promise.race([requestImg(), timeout()]).then((data) =>{
            console.log(data);
        }).catch((err) => {
            console.log(err);
        });
    
    • requestImg函数会异步请求一张图片,把地址写为"图片的路径",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是它俩就会赛跑。
    • 如果5秒之内图片请求成功了,那么进入then方法,执行正常的流程。
    • 如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息

Promise.any

  • Promise.race类似,区别在于它只等待第一个 fulfilled 的 promise,并将这个 fulfilled 的 promise 返回
  • 如果给出的 promise 都 rejected,那么返回的 promise 会带有 AggregateError —— 一个特殊的 error 对象,在其 errors 属性中存储着所有 promise error

Promise.resolve

  • 给定 value 创建一个resolvedpromise

Promise.reject

  • 给定 error 创建一个 rejected promise

Promisification

  • 将一个接受回调的函数转换为一个返回 promise 的函数
  • Promisification 是一种很好的方法,特别是在使用 async/await 的时候,但不是回调的完全替代
  • 一个promise可能只有一个结果,但从技术上讲,一个回调可能被调用很多次。因此,promisification 适用于调用一次回调的函数,进一步的调用将被忽略。

微任务

  • promise 的处理程序 .then.catch.finally 都是异步的。即便一个 promise 立即被 resolve,.then.catch.finally 下面 的代码也会在这些处理程序之前被执行。

    let promise = Promise.resolve();
    promise.then(() => alert("promise done!"));
    alert("code finished"); // 这个 alert 先显示
    
  • 异步任务需要适当的管理,ECMA 标准规定了一个内部队列 PromiseJobs,通常被称为微任务队列。当一个 promise 准备就绪时,它的 .then/catch/finally 处理程序就会被放入队列中,但是它们不会立即被执行。当 JavaScript 引擎执行完当前的代码,它会从队列中获取任务并执行它。

  • 如果有一个包含多个 .then/catch/finally 的链,那么它们中的每一个都是异步执行的。也就是说,它会首先进入队列,然后在当前代码执行完成并且先前排队的处理程序都完成时才会被执行。

  • 想让 code finishedpromise done 之后出现,就要把.then放入队列里

    Promise.resolve()
      .then(() => alert("promise done!"))
      .then(() => alert("code finished"));
    
  • 现在可以解释 JavaScript 是如何发现未处理的 rejection 的。如果一个 promise 的 error 未被在微任务队列的末尾进行处理,则会出现“未处理的 rejection”

  • 正常来说,我们会在 promise 链上添加 .catch 来处理 error:

    let promise = Promise.reject(new Error("Promise Failed!"));
    promise.catch(err => alert('caught'));
    
    // 不会运行:error 已经被处理
    window.addEventListener('unhandledrejection', event => alert(event.reason));
    
  • 忘记添加 .catch,那么,微任务队列清空后,JavaScript 引擎会触发下面这事件:

    let promise = Promise.reject(new Error("Promise Failed!"));
    
    // Promise Failed!
    window.addEventListener('unhandledrejection', event => alert(event.reason));
    
  • 如果迟一点再处理:

    let promise = Promise.reject(new Error("Promise Failed!"));
    setTimeout(() => promise.catch(err => alert('caught')), 1000);
    
    // Error: Promise Failed!
    window.addEventListener('unhandledrejection', event => alert(event.reason));
    
    • 我们会先看到 Promise Failed!,然后才是 caught
    • 当微任务队列中的任务都完成时,才会生成 unhandledrejection:引擎会检查 promise,如果 promise 中的任意一个出现 rejected状态,unhandledrejection 事件就会被触发
    • 在上面这个例子中,被添加到 setTimeout 中的 .catch 也会被触发,只是会在 unhandledrejection 事件出现之后才会被触发,所以它并没有改变什么(没有发挥作用)

async / await

  • async / await 是以更舒适的方式使用 promise 的一种特殊语法

  • async 可以被放置在一个函数前面,即这个函数总是返回一个 promise,其他值将自动被包装在一个 resolved 的 promise 中

  • await只在 async 函数内工作,它让 JavaScript 引擎等待直到 promise 完成(settle)并返回结果。如果有 error,就会抛出异常,就像那里调用了 throw error 一样

  • await 实际上会暂停函数的执行,直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值