Promise入门到自定义

Promise介绍与基本使用

Promise是什么?

理解:

  1. 抽象表达
    • Promise 是一门新的技术(ES6规范)
    • Promise是JS中进行异步编程的 新解决方案 备注:旧方案使用回调函数
  2. 具体表达
    • 从功能语法上来说:Promise是一个构造函数,可以进行对象的实例化。
    • 从功能上来说:Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值。
    • 异步操作:
      • fs 文件操作(node.js下的一个模块,对计算机的磁盘进行读写操作)
      • 数据库操作
      • AJAX网络请求
      • 定时器

为什么用Promise?

指定回调函数的方式更加灵活

  1. 旧的:必须在启动异步任务前指定。
  2. promise:启动异步任务=>返回promise对象=>给Promise对象绑定回调函数(甚至可以在异步任务结束后指定多个)

支持链式调用,可以解决回调地狱问题

  1. 什么是回调地狱?

    回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行条件(一个回调套着另一个异步任务)

  2. 回调地狱的缺点? 不便于阅读 不便于异常处理

  3. 解决方案 promise链式调用

  4. 终级解决方案

Promise初体验

javascript 复制代码
  <script>
    function runAsyns ()
    {
      //我们使用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数
      //执行这个函数我们得到了一个Promise对象  
      var p = new Promise(function (resolve, reject)
      {
        //做一些异步操作
        setTimeout(function ()
        {
          console.log('执行完成');
          resolve('随便什么数据')
        }, 2000)

      });
      return p;

    }
    //这里then接收一个参数,是函数,并且会拿到我们在runAsyns中调用resolve时
    //传的参数,运行这段代码会在2s后输出"执行完成",进阶着输出"随便什么数据"  
    runAsyns().then(function (data)
    {
      console.log(data);
      //这里  随便什么数据就在控制台出来了
      //后面传来的数据可以做些其他操作
    })
  </script>

这时候你应该有所领悟了,原来then里面的函数就跟我们平时的回调函数一个意思,能够在runAsync这个异步任务执行完成之后被执行。这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。

你可能会不屑一顾Promise就这点能耐?我把回调函数封装一下,给runAsync传进去不也一样吗,就像这样:

javascript 复制代码
function runAsync(callback){
    setTimeout(function(){
        console.log('执行完成');
        callback('随便什么数据');
    }, 2000);
}

runAsync(function(data){
    console.log(data);
});

效果也是一样的,还费劲用Promise干嘛。那么问题来了,有多层回调该怎么办?如果callback也是一个异步操作,而且执行完后也需要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,然后给callback传进去吧。而Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。

链式操作的用法

所以,从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是"状态",用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:

javascript 复制代码
  <script>
    function runAsync1 ()
    {
      var p = new Promise(function (resolve, reject)
      {
        //做一些异步操作
        setTimeout(function ()
        {
          console.log('异步任务1执行完成');
          resolve('随便什么数据1');
        }, 1000);
      });
      return p;
    }

    function runAsync2 ()
    {
      var p = new Promise(function (resolve, reject)
      {
        //做一些异步操作
        setTimeout(function ()
        {
          console.log('异步任务2执行完成');
          resolve('随便什么数据2');
        }, 2000);
      });
      return p;
    }

    function runAsync3 ()
    {
      var p = new Promise(function (resolve, reject)
      {
        //做一些异步操作
        setTimeout(function ()
        {
          console.log('异步任务3执行完成');
          resolve('随便什么数据3');
        }, 2000);
      });
      return p;
    }
    runAsync1()
      .then(function (data)
      {
        console.log(data);
        return runAsync2();
      })

      .then(function (data)
      {
        console.log(data);
        return runAsync3();
      })
      .then(function (data)
      {
        console.log(data);

      });
  </script>

Promise封装AJAX请求

javascript 复制代码
 <script>
    //  封装一个sendAJAX 发送GET AJAX请求。参数URL 返回结果Promise对象
    function sendAJAX (url)
    {
      return new Promise((resolve, reject) =>
      {
        const xhr = new XMLHttpRequest();
        xhr.responseType = 'json';
        xhr.open("GET", url);
        xhr.send();
        //处理结果
        xhr.onreadystatechange = function ()
        {
          if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300) {
              //成功的结果
              resolve(xhr.response);
            } else {
              reject(xhr.status);
            }
          }
        }

      })
    }
    sendAJAX('http://api.apiopen.top/getJoke')
      .then(value =>
      {
        console.log(value);
      }, reason =>
      {
        console.log(reason);
      })
  </script>

Promise对象的值

实例对象中的另一个属性【PromiseResult】 里面存的是异步任务对象成功/失败的结果 可以修改此属性的值:

  • resolve
  • reject

Promise的基本流程

Promise API

Promise构造函数:Promise(excutor){}

  1. executor函数:执行器(resolve,reject)=>{}(不会进到队列当中去,会立即执行 同步调用的)
  2. resolve函数:内部定义成功时我们调用的函数value=>{}
  3. reject函数:内部定义失败时我们调用的函数reason=>{} 说明:executor会在Promise内部立即同步调用,异步操作在执行器中执行。

Promise.prototype.then方法:(onResolved,onRejected)=>{}

  1. onResolved函数:成功的回调函数(value)=>{}
  2. onRejected函数:失败的回调函数(reason)=>{} 说明:指定用于得到成功value的回调和用于得到失败reason的失败回调返回一个新的promise对象。

Promise.prototype.catch方法:

(onRejected)=>{}(只能指定失败的回调) 和then的第二个参数一样,用来指定reject的回调。

Promise.resolve方法:(value)=>{}

属于Promise函数对象,不属于Promise实例对象 value:成功的数据或promise对象 说明:返回一个成功/失败的promise对象

Promise.reject方法:(reason)=>{}

我们前面的例子都是只有"执行成功"的回调,还没有"失败"的情况,reject的作用就是把Promise的状态设置为rejected,这样我们在then中就能捕捉到,然后执行"失败情况的回调",返回一个失败的Promise对象 reject方法:

javascript 复制代码
 <script>
    function getNumber ()
    {
      var p = new Promise(function (resolve, reject)
      {
        //做一些异步操作
        setTimeout(function ()
        {
          //Math.ceil() 函数返回大于或等于一个给定数字的最小整数
          var num = Math.ceil(Math.random() * 10) //生成1-10的随机数
          if (num <= 5) {
            resolve(num)
          } else {
            reject('数字太大了ffffffffff')
          }

        }, 2000)
      })
      return p;
    }
    getNumber()
      .then(
        function (value)
        {

          console.log('resolved');
          // console.log(data);
          console.log(value, 'dddd');
        },

        function (reason, data)
        {

          console.log('rejected');
          console.log(reason);
          console.log(data, 'aew');

        }
      )
  </script>

getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是"成功"了,调用resolve修改Promise的状态。否则我们认为是"失败"了,调用reject并传递一个参数,作为失败的原因。

运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:

Promise.all方法:(promises) =>{}

promises:包含n个promise的数组 说明:返回一个新的promise,只有所有的promise都成功才成功,只要又一个失败了就直接失败

all的用法 Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,看下面的例子

javascript 复制代码
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。所以上面代码的输出结果就是:

有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

Promise.race方法:(promises)=>{}

promises:包含n个promise的数组 说明:返回一个新的promise,第一个完成的promise的结果就是臮的结果状态。

race的用法 all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:

javascript 复制代码
在这里插入代码片Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

这三个异步操作同样是并行执行的。结果你应该可以猜到,1秒后runAsync1已经执行完了,此时then里面的就执行了。结果是这样的: 是吧。在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。 这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

javascript 复制代码
//请求某个图片资源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}

//延时函数,用于给请求计时
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

catch

javascript 复制代码
getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:

javascript 复制代码
getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果: 也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

util.promisify(original)

传入一个遵循常见的错误优先的回调风格(即以(err,value)=>...回调作为最后一个参数),并返回一个Promise的版本。

Promise关键问题

Promise的状态改变

  • pending变为resolved
  • pending变为rejected 说明:只有这两种,且一个promise对象只能改变一次 无论变为成功还是失败,都会有一个结果数据 成功的结果数据一般称为value失败的结果一般称为reason 实例对象中的一个属性【PromiseState】
  • pending未决定的
  • resolved/fullfilled成功
  • rejected 失败

一个promise指定多个成功/失败回调函数,都会调用吗?

当promise改变为对应状态时都会调用

改变promise状态和指定回调函数谁先谁后?

  1. 都有可能,正常情况下时先指定回调再改变状态,但也可以先改状态再指定回调。
  2. 如何先改状态再指定回调?
    • 在执行器中直接调用resolve()/reject()
    • 延迟更长时间才调用then()
  3. 什么时候才能得到数据?
    • 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
    • 如果先改变的状态,拿当指定回调时,回调函数就会调用,得到数据

Promise.then()返回新的Promise的结果状态由什么决定?

1.简单表达:由then()指定的回调函数执行的结果决定 2 详细表达 - 如果抛出异常,新Promise变为rejected,reason为抛出的异常 - 如果返回的是非promise的任意值,新promise变为resolved,value为返回的值 - 如果返回的是另一个新promise,此promise的结果就会成为新promise的结果

Promise如何串联多个操作任务?

scss 复制代码
- Promise的then()返回一个新的Promise,可以开成then()的链式调用
- 通过then的链式调用串联多个同步/异步任务

Promise异常穿透

1.当使用Promise的then链式调用时,可以在最后指定失败的回调。 2.前面任何操作出了异常,都会传到最后失败的回调中处理。

中断promise链?

1.当使用Promise的then链式调用时,在中间间断,不再调用后面的回调参数 2.办法:在回调函数中返回一个pending状态的Promise对象。

Promise自定义封装

javascript 复制代码
//上述代码中,首先定义了一个myPromise函数,该函数接收一个参数fn,表示异步操作。然后定义了state和value两个变量,用于保存Promise的状态和结果。callbacks数组用于保存then方法中传入的回调函数。
function myPromise(fn) {
  let state = 'pending';
  let value = null;
  const callbacks = [];
//接着定义了then方法,该方法接受一个回调函数作为参数,并返回一个新的Promise对象。在then方法中,通过handle函数将回调函数保存到callbacks数组中。
  this.then = function(callback) {
    return new myPromise(resolve => {
      handle({
        onFulfilled: callback || null,
        resolve: resolve
      });
    });
  };
//handle函数用于处理回调函数,当Promise状态为pending时,将回调函数保存到callbacks数组中。等状态改变后再执行。当状态为fulfilled时,执行回调函数并返回结果。
  function handle(callback) {
    if (state === 'pending') {
      callbacks.push(callback);
      return;
    }
    if (!callback.onFulfilled) {
      callback.resolve(value);
      return;
    }
    const ret = callback.onFulfilled(value);
    callback.resolve(ret);
  }
//resolve函数用于改变Promise的状态和结果,当传入参数为Promise对象时,递归调用resolve函数,直到传入的参数为普通值位置。然后改变Promise状态和结果,并异步执行回调函数。
  function resolve(newValue) {
    if (newValue && typeof newValue === 'object' && typeof newValue.then === 'function') {
      newValue.then(resolve);
      return;
    }

    value = newValue;
    state = 'fulfilled';
    setTimeout(() => {
      callbacks.forEach(callback => {
        handle(callback);
      });
    }, 0);
  }

  fn(resolve);
}

使用示例:

javascript 复制代码
const promise = new myPromise(resolve => {
  setTimeout(() => {
    resolve('Hello, Promise!');
  }, 1000);
});

promise.then(value => {
  console.log(value);
})
.then(() => {
  console.log('Done!');
});

定义整体结构(有点难跳过)

async 与await

async函数:

  1. 函数的返回值为promise对象
  2. promise对象的结果由async函数执行的返回值决定

await表达式 1.await右侧的表达式一般为promise对象,但也可以是其他值 2.如果表达式时promise对象,await返回的是promise成功的值 3.如果表达式时其他值,直接将此值作为await的返回值。 注意: 1.await必须写在async函数中,但async函数可以没有await 2.如果await的promise失败了,就会抛出异常,需要通过try...catch捕获处理

async和await的结合 读取resource文件下的3个html文件内容 用回调函数方式实现: aysnc和await结合方式:

async和await结合发送AJAX请求

javascript 复制代码
async function fetchData() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

fetchData();

上述代码中,定义了一个 fetchData 函数,使用 async 关键字标识为异步函数。在函数内部,使用 try-catch 语句处理异步操作的错误。

使用 await 关键字可以等待异步操作完成,并返回结果。这里使用 fetch 函数发送 AJAX 请求,返回的是一个 Promise 对象,使用 await 等待其响应结果。然后使用 await 将响应结果转换为 JSON 格式,最后打印结果。

在函数调用时,只需要调用 fetchData 函数即可,不需要像 Promise 那样使用链式调用。

值得注意的是,使用 async 和 await 发送 AJAX 请求需要使用 Promise 对象。在上述代码中,fetch 函数返回的就是一个 Promise 对象,因此可以使用 await 等待其响应结果。

相关推荐
JohnYan10 分钟前
工作笔记 - 改进的单例应用
javascript·设计模式·bun
鹧鸪yy17 分钟前
从Token介绍到单点登录SSO
前端·javascript
一块plus35 分钟前
创造 Solidity、提出 Web3 的他回来了!Gavin Wood 这次将带领波卡走向何处?
javascript·后端·面试
老虎06271 小时前
JavaWeb前端03(Ajax概念及在前端开发时应用)
前端·javascript·ajax
小鸡脚来咯1 小时前
react速成
前端·javascript·react.js
剽悍一小兔1 小时前
React15.x版本 子组件调用父组件的方法,从props中拿的,这个方法里面有个setState,结果调用报错
前端·javascript·react.js
神笔码农nice1 小时前
VUE从入门到精通二:ref、reactive、computed计算属性、watch监听、组件之间的通信
前端·javascript·vue.js
Zayn2 小时前
JavaScript 小数精度问题
前端·javascript
Maxkim2 小时前
🐳 前端工程师的后端小实验:Docker + Sequelize 玩转 MySQL API 🚀
javascript·后端
11054654012 小时前
35、自主移动机器人 (AMR) 调度模拟 (电子厂) - /物流与仓储组件/amr-scheduling-electronics
前端·javascript