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 等待其响应结果。

相关推荐
hhzz1 小时前
vue前端项目中实现电子签名功能(附完整源码)
前端·javascript·vue.js
秋雨凉人心1 小时前
上传npm包加强
开发语言·前端·javascript·webpack·npm·node.js
JoeChen.2 小时前
PostCSS插件——postcss-pxtorem结合动态调整rem实现字体自适应
javascript·ecmascript·postcss
前端切圖仔2 小时前
失业,仲裁,都赶上了(二)
前端·javascript·程序员
slongzhang_2 小时前
elementPlus消息组件多按钮案例
前端·javascript·vue.js
会发光的猪。3 小时前
vue中el-select选择框带搜索和输入,根据用户输入的值显示下拉列表
前端·javascript·vue.js·elementui
PP东3 小时前
ES学习class类用法(十一)
javascript·学习
海威的技术博客3 小时前
JS中的原型与原型链
开发语言·javascript·原型模式
outstanding木槿4 小时前
JS中for循环里的ajax请求不数据
前端·javascript·react.js·ajax
酥饼~4 小时前
html固定头和第一列简单例子
前端·javascript·html