探秘Promise

@探秘Promisejavascript

create by db on 2019-8-18 18:54:39
Recently revised in 2019-9-2 21:51:02前端

Hello 小伙伴们,若是以为本文还不错,麻烦点个赞或者给个 star,大家的赞和 star 是我前进的动力!GitHub 地址java

 查阅网上诸多资料,并结合本身的学习经验,写下这篇学习笔记,以记录本身的学习心得。现分享给你们,以供参考。node

前言

I hear and I fogorget.jquery

I see and I remember.git

I do and I understand.github

 咱们都知道,js的世界是单线程执行的,也就是说一个任务完成以后才能进行另外一个任务,这是由于js是运行在宿主进程多脚本语言,好比浏览器,好比node,宿主进程只会为其分配一个js引擎线程。ajax

 那么对于耗时比较长的操做,例如一些ajax异步请求,这些请求之间有一种关系,就是下一次请求的参数是上一次请求的结果——回调地狱。所幸ES6给咱们提供了异步编程的一种解决方案——Promise。编程

 参考文献json

正文

什么是Promise

Promise的含义

 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最先提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

 所谓promise,简单说是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果,从语法上说,promise是一个对象,从它能够获取异步操做的消息,promise提供了统一的API,各类异步操做均可以用一样的方法进行处理。

Promise对象的特色

Promise的优势

  1. 对象的状态不受外界影响。promise对象表明一个异步操做,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态,这也是promise这个名字的由来——“承诺”;

  2. 一旦状态改变就不会再变,任什么时候候均可以获得这个结果。promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。这时就称为resolved(已定型)。若是改变已经发生了,你再对promise对象添加回调函数,也会当即获得这个结果。这与事件(event)彻底不一样,事件的特色是:若是你错过了它,再去监听是得不到结果的。

 有了Promise对象,就能够将异步操做以同步操做的流程表达出来,可使用一种链式调用的方式来组织代码,避免了层层嵌套的回调函数。让代码更加的直观。此外,Promise对象提供统一的接口,使得控制异步操做更加容易。

Promise的缺点

  1. 没法取消Promise,一旦新建它就会当即执行,没法中途取消。
  2. 若是不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  3. 当处于pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。

那咱们为何要使用promise?

好比咱们在工做中常常会碰到这么一个需求,好比我使用ajax发一个A请求后,成功后拿到数据,咱们须要把数据传给B请求;那么咱们须要以下编写代码:

$.ajax({
    url: '',
    dataType:'json',
    success: function(data) { // 获取data数据 传给下一个请求
      var id = data.id;
      $.ajax({
        url:'',
        data:{"id":id},
        success:function(){ // .....
 }
      });
    }
});
复制代码

 如上代码;上面的代码有以下几点缺点:

  1. 后一个请求须要依赖于前一个请求成功后,将数据往下传递,会致使多个ajax请求嵌套的状况,代码不够直观。
  2. 若是先后两个请求不须要传递参数的状况下,那么后一个请求也须要前一个请求成功后再执行下一步操做,这种状况下,那么也须要如上编写代码,致使代码不够直观。

如何建立promise对象?

 要想建立promise对象,可使用new来调用promise的构造器来进行实例化。

以下代码:

var promise = new Promise(function(resolve,reject){
  // ... some code
  if(/*异步操做成功*/) {
  resolve(value) // 成功调用resolve 往下传递参数 且只接受一个参数
  }else {
  reject(error)  // 失败调用reject 往下传递参数 且只接受一个参数
  }   
});
复制代码

 对经过new 生成的promise对象为了设置其值在resolve(成功) / reject(失败) 时调用的回调函数,可使用promise.then()实例方法。

以下代码:

promise.then(onFulfilled, onRejected);
复制代码

resolve(成功) 时 调用onFulfilled 方法,reject(失败) 时 调用onRejected方法;

Promise.then 成功和失败时均可以使用,若是出现异常的状况下能够采用promise.then(undefined,onRejected) 这种方式,只指定onRejected回调函数便可,不过针对这种状况下咱们有更好的选择是使用catch这个方法;代码以下:

promise.catch(onRejected);
复制代码

 上面啰嗦了这么多,咱们来分别来学习相关的promise对象中的方法知识点吧!

Promise.resolve

 通常状况下咱们都会使用new Promise()来建立promise对象,可是咱们也可使用promise.resolvepromise.reject这两个方法;

Promise.resolve(value)的返回值也是一个promise对象,咱们能够对返回值进行.then调用;

以下代码:

Promise.resolve(11).then(function(value){
  console.log(value); // 打印出11
});
复制代码

resolve(11)代码中,会让promise对象进入肯定(resolve状态),并将参数11传递给后面的then所指定的onFulfilled 函数;

 咱们上面说过建立promise对象,可使用new Promise的形式建立对象,可是咱们这边也可使用Promise.resolve(value)的形式建立promise对象;

Promise.reject

Promise.reject 也是new Promise的快捷形式,也建立一个promise对象。

好比以下代码:

Promise.reject(new Error(“我错了,请原谅俺!!”));
复制代码

就是下面的代码new Promise的简单形式:

new Promise(function(resolve,reject){
   reject(new Error("我错了,请原谅俺!!"));
});
复制代码

 下面咱们来综合看看使用resolve方法和reject方法。

demo以下:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法调用
testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});
复制代码

 上面的代码的含义是给testPromise方法传递一个参数,返回一个promise对象,若是为true的话,那么调用promise对象中的resolve()方法,而且把其中的参数传递给后面的then第一个函数内,所以打印出 “hello world”, 若是为false的话,会调用promise对象中的reject()方法,则会进入then的第二个函数内,会打印No thanks

理解Promise异步调用的操做

var promise = new Promise(function(resolve){
  console.log(1);
  resolve(3);
});
promise.then(function(value){
  console.log(value);
});
console.log(2);
复制代码

 上面的代码输出咱们能够看到,结果分别为1,2,3

 首先代码从上往下执行,首先输出1,而后调用resolve(3)这个方法,这时候promise对象变为肯定状态,即调用onFulFilled这个方法,从上面了解到,resolve(成功) 时 调用onFulfilled 方法,Promise.then 成功和失败时均可以使用,所以第一个函数是成功调用的,可是Promise对象是以异步方式调用的,因此先执行console.log(2),输出的是2,而后输出的是3

理解是同步调用仍是异步调用

function ready(fn){
  var readyState = document.readyState;
  if (readyState === 'interactive' || readyState === 'complete') {
    fn();
  } else {
    window.addEventListener('DOMContentLoaded', fn);
  }
}
ready(function(){
  console.log("DOM Load Success");
});
console.log("我是同步输出的");
复制代码

 如上代码;若是在调用ready()方法以前DOM已经载入完成的话,就会对回调函数进行同步调用,先输出DOM Load Success后输出 我是同步输出的 ;若是在调用ready()方法以前DOM为未载入完成的话,那么代码先会执行 window.addEventListener(‘DOMContentLoaded’, fn); 就会异步调用该函数,那么就会先输出 “我是同步输出的”,后输出“DOM Load Success”; 为了解决上面的同步或者异步混乱的问题,咱们如今可使用promise对象使用异步的方式来解决;

以下代码:

function readyPromise(){
  return new Promise(function(resolve,reject){
    var readyState = document.readyState;
    if (readyState === 'interactive' || readyState === 'complete') {
      resolve();
    } else {
      window.addEventListener('DOMContentLoaded', resolve);
    }
  });
}
readyPromise().then(function(){
  console.log("DOM Load Success");
});
console.log("我是同步加载的,先执行我");
复制代码

 输出以下:先输出“我是同步加载的,先执行我” 后输出 “DOM Load Success”。由于promise对象是异步加载的。

理解promise的三种状态

Promise 对象有三种状态:

  • Resolve能够理解为成功的状态;
  • Rejected 能够理解为失败的状态;
  • Pending既不是Resolve也不是Rejected状态;能够理解为Promise对象实例建立时候的初始状态;

 好比Promise对象中的resolve方法就是调用then对象的第一个函数,也就是成功的状态;而reject方法就是调用then对象的第二个函数,也就是失败的状态;

理解then()

 仍是以前的例子:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法调用
testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});
复制代码

 上面的代码就是利用了 then(onFulfilled,onRejected)方法来执行的,第一个方法就是成功状态的标志,第二个方法是失败的状态标志;

 固然在多个任务的状况下then方法一样可使用;好比上面的代码改为以下:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法调用
testPromise(true).then(function(msg){
  console.log(msg);
}).then(testPromise2)
  .then(testPromise3);
function testPromise2(){
  console.log(2);
}
function testPromise3(){
  console.log(3);
}
复制代码

输出以下:hello world ,2,3

 上面的代码是then的链式调用方式,输出是按顺序输出的 分别为 hello world , 2,3; 使用链式调用的缘由是每次调用后都会返回promise对象;

理解Promise.catch()方法

Promise.catch()方法是promise.then(undefined,onRejected)方法的一个别名,该方法用来注册当promise对象状态变为Rejected的回调函数。

以下代码:

var promise = Promise.reject(new Error("message"));
promise.catch(function(error) {
  console.log(error);
});
复制代码

 无论是then仍是catch方法调用,都返回一个新的promise对象;

下面咱们来看看这个例子:

var promise1 = new Promise(function(resolve){
  resolve(1);
});
var thenPromise = promise1.then(function(value){
  console.log(value);
});
var catchPromise = thenPromise.catch(function(error){
  console.log(error);
});
console.log(promise1 !== thenPromise); // true
console.log(thenPromise !== catchPromise); //true
复制代码

 如上代码,打印的都是true,这说明无论是then仍是catch都返回了和新建立的promise是不一样的对象;

 若是咱们知道了then方法每次都会建立返回一个新的promise对象的话,那么久不难理解下面的代码了;

以下:

var promise1 = new Promise(function(resolve){
  resolve(1);
});
promise1.then(function(value){
  return value * 2;
});
promise1.then(function(value){
  return value * 2;
});
promise1.then(function(value){
  console.log("1"+value);
});
复制代码

 如上的代码;打印出11;由于他们每次调用then方法时,是使用的不一样的promise对象;所以最后打印的value仍是1;可是若是咱们then方法是连续调用的话,那状况就不同了。

好比以下代码:

var promise1 = new Promise(function(resolve){
  resolve(2);
});
promise1.then(function(value){
  return value * 2;
}).then(function(value){
  return value * 2;
}).then(function(value){
  console.log("1"+value);
});
复制代码

 打印出18,即 “1” + 2 * 2 * 2 = 18;

 上面第一种方法没有使用方法链的调用,上面第一种那种写法then 调用几乎是同时开始进行的,且传给每一个then的value都是1

 第二种方式是使用方法链的then,使多个then方法链接在一块儿了,所以函数会严格执行 resolve – then — then – then的顺序执行,而且传递每一个then方法的value的值都是前一个promise对象中return的值;所以最后的结果就是18了;

 如今咱们再回过头一刚开始咱们讨论的为何要使用promise的缘由的问题了,好比2个ajax请求,后一个ajax请求须要获取到前一个ajax请求的数据,咱们以前在使用jquery写代码是以下的:

$.ajax({
   url: '',
   dataType:'json',
   success: function(data) {
  // 获取data数据 传给下一个请求
  var id = data.id;
  $.ajax({
    url:'',
    data:{"id":id},
    success:function(){
      // .....
    }
  });
  }
});
复制代码

 如今咱们学习了then方法后,咱们能够从新编写上面的代码变成以下:

var ajaxPromise = new Promise(function(resolve){
  resolve();
});
ajaxPromise.then(function(){
  $.ajax({
    url:'',
    dataType:'json',
    success: function(data) {
      var id = data.id;
      return id;
    }
  })
}).then(function(id){
  $.ajax({
    url:'',
    dataType:'json',
    data:{"id":id},
    success: function(data){
      console.log(data);
    }
  })
});
复制代码

理解Promise.all

Promise.all方法用于将多个Promise实例包装成一个新的Promise实例。

 Promise.all能够接受一个元素为Promise对象的数组做为参数,当这个数组里面全部的promise对象都变为resolve时,该方法才会返回。

以下代码:

var promise1 = new Promise(function(resolve){
  setTimeout(function(){
    resolve(1);
  },3000);
});
var promise2 = new Promise(function(resolve){
  setTimeout(function(){
    resolve(2);
  },1000);
});
Promise.all([promise1,promise2]).then(function(value){
  console.log(value); // 打印[1,2]
});
复制代码

 如上代码 打印的是[1,2]; 如上咱们看到promise1对象中的setTimeout是3秒的时间,而promise2对象中的setTimeout是1秒的时间,可是在Promise.all方法中会按照数组的原先顺序将结果返回;

 在咱们平时的需求中,或许有这种状况的需求,好比咱们须要发2个ajax请求时,无论他们的前后顺序,当这2个ajax请求都同时成功后,咱们须要执行某些操做的状况下,这种状况很是适合。

理解Promise.race

 如上可知:Promise.all在接收到的全部对象promise都变为FulFilled或者 Rejected状态以后才会继续后面的处理,可是Promise.race的含义是只要有一个promise对象进入FulFilled或者Rejected状态的话,程序就会中止,且会继续后面的处理逻辑;

以下代码:

// `delay`毫秒后执行resolve
function timerPromise(delay){
  return new Promise(function(resolve){
    setTimeout(function(){
      resolve(delay);
    },delay);
  });
}
// 任何一个promise变为resolve或reject 的话程序就中止运行
Promise.race([
  timerPromise(1),
  timerPromise(32),
  timerPromise(64),
  timerPromise(128)
]).then(function (value) {
  console.log(value);  // => 1
});
复制代码

 如上代码建立了4个promise对象,这些promise对象分别在1ms,32ms,64ms,128ms后变为肯定状态,而且在第一个变为肯定状态后1ms后,then函数就会被调用,这时候resolve()方法给传递的值为1,所以执行then的回调函数后,值变为1

 咱们再来看看当一个promise对象变为肯定状态(FulFiled)的时候,他们后面的promise对象是否还在运行呢?咱们继续看以下代码运行:

var runPromise = new Promise(function(resolve){
  setTimeout(function(){
    console.log(1);
    resolve(2);
  },500);
});
var runPromise2 = new Promise(function(resolve){
  setTimeout(function(){
    console.log(3);
    resolve(4);
  },1000);
});

// 第一个promise变为resolve后程序中止
Promise.race([runPromise,runPromise2]).then(function(value){
  console.log(value);
});
复制代码

 如上代码是使用定时器调用的,上面是2个promise对象,咱们看到第一个promise对象过500毫秒后加入到执行队列里面去,若是执行队列没有其余线程在运行的时候,就执行该定时器,因此第一次打印1,而后调用resolve(2); 接着调用promise.race方法,该方法只要有一个变为成功状态(FulFiled)的时候,程序就会中止,所以打印出2,同时后面的promise对象接着执行,所以打印出3,可是因为promise.race()该方法已经中止调用了,因此resolve(4)不会有任何输出;所以最后输出的是1,2,3

 由此咱们得出结论:当一个promise对象变为(FulFilled)成功状态的时候,后面的promise对象并无中止运行。

总结

 做为javascript的新标准,ES6为咱们提供了不少新语法及新特性,让咱们的代码更加简单与易用。

 做为一只前端菜鸟,本篇文章旨在记录本身的学习心得,若有不足,还请多多指教,谢谢你们。

 路漫漫其修远兮,与诸君共勉。

后记:Hello 小伙伴们,若是以为本文还不错,记得点个赞或者给个 star,大家的赞和 star 是我编写更多更丰富文章的动力!GitHub 地址

知识共享许可协议
db 的文档库http://www.javashuo.com/tag/db 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/danygitgit上的做品创做。
本许可协议受权以外的使用权限能够从 creativecommons.org/licenses/by… 处得到。

相关文章
相关标签/搜索