js promise 逐步了解

背景

是一种异步的实现方式, 下面会讲4中实现异步, 然后过渡到promise . 这也是选择promise 的原因。

先来看下setTimeout

setTimeout 是异步代码, 以前不理解的时候一直以为是同步的。看下一步效果代码

<script>
  function f1() {
    setTimeout(function () {
      alert(100)
    }, 3000)
  }
  f1()
  alert(99)
</script>

这就说明了是异步的, 下面就可以使用setTimeout 实现一些异步操作。

了解一下

javascript 语言的执行环境是单线程的, 就是一次只能执行一个任务。如果有多个任务就必须排队, 前面一个任务执行完了再执行下一个。缺点就是任务耗时长, 造成浏览器无响应 卡死, 就是因为代码是长时间运行(死循环)导致整个页面卡死。
为了解决这个问题js语言将任务的执行模式分为:同步异步

同步就是一个任务执行完毕另外一个再执行, 程序的执行顺序和任务的执行顺序是一致的同步的。
异步, 每个任务都有一个或者多个回调函数(callback), 前一个任务结束后不是执行吼一个任务, 而是执行回调函数,后一个任务不等前一个任务就执行, 所以程序的执行顺序和任务的排列顺序不是一致不同步的。
异步模式很重要,在浏览器端耗时操作都应该是异步操作的, 避免浏览器失去响应, 最好的例子就是ajax 操作。

四种异步编程模式比较 然后到promise

1 回调函数

异步编程最基本的方法
假设有连个函数f1 f2, 后者等待前者的执行结果

f1()
f2()

f1 是一个耗时的操作, 可以改写f1 把f2写成f1的回调函数。

<script>
  function f1(callback) {
    setTimeout(function () {
      //f1 的执行代码
      // 然后执行回调函数
      callback()
    })
  }
</script>

//调用
f1(f2);

采用这种方式同步变成了异步, f1 不会阻塞程序执行。回调函数的有点事简单 容易理解和部署。 缺点就是不利于阅读和代码维护。 各个部分之间高度融合,流程会比较乱。
而且每一个任务只能指定一个回调函数

事件监听

事件驱动模式, 任务的执行不取决于代码的顺序,而是取决于时间是否发生。
以f1 f2 为例子 首先以f1 绑定一个事件(采用jquery的写法)

f1.on("done", f2)

意思是当f1 发生done事件f2. 改写

<script>
  function f1() {
    setTimeout(function () {
      //f1 要执行的代码块
      f1.trigger("done")
    }, 1000)
  }
  f1.on("done", f2)
</script>

f1.trigger(‘done’)表示, 执行完成后, 立即触发done事件, 从而执行f2
这中方法有点是可以绑定多个事件, 每个事件可以指定多个回调函数, 而且可以去耦合, 有利于实现模块化。缺点是整个程序都变成了事件驱动类型, 运行流程变得很不清晰。

发布订阅

上面的事件可以理解为“信号”

我们假定存在一个“信号中心”, 某个任务完成, 就向信号中心“发布publish”这个信号, 其他任务可以向信号中心“订阅subscribe” 这个信号, 从而可以知道什么时候可以执行。 这就叫 发布订阅模式 又叫观察者模式

jQuery.subscribe("done", f2);

然后f1 进行改写

 function f1(){

    setTimeout(function () {

      // f1的任务代码

      jQuery.publish("done");

    }, 1000);

  }

jQuery.publish(“done”)的意思是,f1执行完成后,向”信号中心”jQuery发布”done”信号,从而引发f2的执行。

此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe(“done”, f2);
这种方法的性质与”事件监听”类似,但是明显优于后者。因为我们可以通过查看”消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行

promise 对象

Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。

他的思想就是, 每个异步任务返回一个promise对象, 该对象有一个then方法, 允许指定回调函数, 比如f1 回调f2 函数

f1().then(f2)

相当于这样的改写(jquery 实现)

 function f1(){

    var dfd = $.Deferred();

    setTimeout(function () {

      // f1的任务代码

      dfd.resolve();

    }, 500);

    return dfd.promise;

  }

这样写的好处是, 回调函数变成了链式写法, 程序的流程可以很清晰。
而且有一整套的配套方法,可以实现许多强大的功能。

比如,指定多个回调函数:

f1().then(f2).then(f3);

再比如指定发生错误时候的回调函数

f1().then(f2).fail(f3)

有一个其他都没有的好处就是,如果一个任务执行完了, 再添加回调函数, 该回调函数会立即执行。 所以不用担心错过某个信号或者事件。 缺点是编写困难理解比较困难。 不过对于老司机这都不是事。

promise中的关键词

Promise resolve reject prepending Resolve Reject then catch all race

简单说promise就是一个容器, 里面包含着一个未来才会结束的事件(通常是一个异步操作)。 这个和tornado 中future有点像。 从语法上面讲, promise是一个对象, 从他可以获取异步操作的消息。promise 提供统一的api , 各种异步操作都可以用用同样的方法进行处理。

promise 的两个特点:
1. 对象的状态不受外界影响。promise 代表一个异步操作, 有3中状态Pending(进行中), Resolved(已经完成 又叫fulfilled) 和Rejected(已经失败)。 只有异步操作的结果, 可以决定当前是哪一种状态, 任何操作都无法改变这一状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  1. 一旦状态改变就不会再变, 任何时候都可以得到这个结果。 promise状态的改变只有两种可能,:从Pending变为Resolved 和从pending 变为Rejected。 只要这两种情况发生了, 状态就凝固啦, , 不会再变了, 会一直保持这个结果。就算改变已经发生, 你再对promise 对象添加回调函数, 也会立即得到这个结果, 这个和时间是不一样的, 事件的特点是如果你错过啦再去监听是得不到结果的,。
    有啦promise对象, 就可以将一部操作以同步的方式表达出来,避免了层层嵌套的回调函数。

回调地狱
在promise之前回调 会有回调地狱的,

setTimeout(function(){ 
left(function(){
 setTimeout(function(){ 
 left(function(){ 
 setTimeout(function(){ left(); },2000); });
 }, 2000);
 }); 
 }, 2000);

这就是传说中的回调地狱,如果有多层逻辑嵌套的话, 不近会使代码阅读变得困难而且后面维护会比较困难。

ES6 Promise诞生啦
promise 和then的用法

 let promise = new Promise(function(resolve, reject) {
    // ... some code
    if (/* 异步操作成功 */){
      resolve(value);
    } else {
      reject(error);
    }
  });

resolve(value) 是promise在已经异步完成成功(resolved)之后执行的。 reject(value) 是在promise 异步失败之后(Rejected)执行。 当然也可以用then来指定:then(resolve, reject) 或者then(resolve),catch(reject)

<script>
  promise.then(function (value) {
    // success
      }, function(error) { 
    // failure
  }); 
  //等价于: 
  promise.then(function(){ 
    //success
  }).catch(function(){ 
    //failure 
  })
</script>

tips

和刚才的回调地狱想比. 不断的去回调

  setTimeout(function () {
    left(function () {
      setTimeout(function () {
        left(function () {
          setTimeout(function () {
            left();
          }, 400)
        })
      },300)
    });
  }, 200)

对比使用promise

<script>
  let p = new Promise((resolve, reject) => {
    setTimeout(resolve, 200)
  })
    .then(()=>setTimeout(null, 2000))
    .then(()=>setTimeout(function () {
      console.log('gogo')
    }, 3000))

</script>

可以采用连续的then链式操作来写回调(因为返回值都是一直都是新的promise实例)

以上例子中可以看出只要早第一个promise回调中添加resolve, 之后的连续then 就会默认执行

在then 中return出数据, 并且这个数据会议参数的的形式传递给下一个then.


<script>
  let p = new Promise(function (resolve, reject) {
    let a = 1
    resolve(a)
  }).then(function (data) {
    console.log(data)
    return ++data
  }).then(function (data) {
    console.log(data)
  })
  //打印出来的结果依次是: 1  2 。
</script>

catch

catch 是用来指定发生错误时候的回调函数(不建议在then 的第二个参数写rejected状态, 总是使用catch)

catch() 使用回调报错时候不会卡死js而是会继续往下执行
promise 对象的错误总是会被下一个catch语句捕获
如:

<script>
  getJsoon('post/1.json').then(function () {
    return getJSON(post.commentURL);
  }).then(function (comments) {
    //some code
  }).catch(function (error) {
    //处理前面promise 产生的错误
  })
</script>

理解下cath

<script>
  let p = new Promise((resolve, reject) => {
    n
  }).then(()=>console.log('运行成功'))
  .catch(()=>{a;console.log('报错')})
  .catch(()=>console.log('报错2'))
  .then(()=>console.log('报错后的回调'))
</script>

首先n没有定义,所以第一层出错。下一个then的‘运行成功’不会被打出来。而是会被下一个catch捕获,第一个catch没有定义a,所以报错,console.log(‘报错’)没办法打出来,又被下一个catch捕获: 第二个catch没有问题:打出‘报错2’。运行成功传给下一个then,打出’报错后的回调’

注意
不管是then 还是catch返回的都是一个新的promise实例, 而每个promise实例都是最原始的Pending 进行中 到resolved(已经完成), 或者pending 进行中到reject(已失败)的过程

其他的一些方法

Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例
let p = Promise.all([p1, p2, p3]);
all()接受数组作为参数。p1,p2,p3都是Promise的实例对象,p要变成Resolved状态需要p1,p2,p3状态都是Resolved,如果p1,p2,p3至少有一个状态是Rejected,p的状态就变成Rejected(个人感觉很想&&符号链接)

Promise.race();
var p = new Promise( [p1,p2,p3] )

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。(感觉就是||符号操作~~~)

Promise resolve():

有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
Promise.resolve等价于下面的写法。

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

Promise.reject('foo')
// 等价于
new Promise(reject => reject('foo'))

注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

猜你喜欢

转载自blog.csdn.net/yangxiaodong88/article/details/80546358