浅谈Promise

时间:2022-09-02 07:57:20

学习过JavaScript的人都知道,JavaScript是单线程作业,这样会有一个很大的缺陷,所有的Ajax,浏览器事件等,都是通过异步去完成。所谓的同步和异步最大的区别无非就是在于同步会阻塞后续代码的执行,然而异步则不会阻塞后续代码的执行。

像setTimeout,setInterval,Ajax这些都是通过异步的回调去做的。拿setTimeout比方来说,及时是把时间设置成0,但是它依然是属于异步方法,任然不会阻塞后续代码的执行。

说到头来无论是setTimeout,Ajax或是其他的异步操作都避免不了的就是回调函数。setTimeout中的function其实就是一个回调函数。

setTimeout(function(){
console.log("我是异步的")
},0)

看一个简单的小例子:

console.log(1)
console.log(2)
console.log(3)
setTimeout(function(){
console.log(4)
},0)
setTimeout(function(){
console.log(5)
},0)
console.log(6)
console.log(7)
console.log(8)

结果:
1,2,3,6,7,8,4,5

如果先看最上面的三行代码,这三行代码是从上之下依次执行的。中间没有任何的异步操作,所以就会依次的打印出1,2,3。接下来就看下整体的代码,在代码中间掺杂了两个异步方法,由于异步操作不会阻塞代码所以就输出了1,2,3,6,7,8,4,5这样的结果。

在做项目的过程中难免会出现一些情况。通过Ajax向后台去请求参数,在返回的参数里面又需要在另一个异步操作里面去使用,依此类推这样的需求如果在同一个页面里面出现N次也就出现了让我们最头疼的一个问题,这也就是我们常说的地狱式回调。

举个例子:

function fn(){
$.ajax({
url:'',
success:funtion(data){
$.ajax({
url:"",
data:data.n,
success:function(data1){
$.ajax({
url:"",
data:data1.n,
success:function(){
......
}
})
}
})
}
})
}

在上面的fn函数中使用了Ajax进行数据请求,服务器返回的参数中,需要用到返回参数里面的n值,才能得到需要的想要的参数,得到n值之后,使用n值作为参数,再次进行ajax请求,依此类推,这里还只是列举了三层,很有可能在实际的项目中会有更多的这样的需求,这样的代码无论是对于后期的维护还是代码的美观性,都是一件很让人头疼的事情,无非就是一场灾难。对于这种情况,还是有解决方案的。

代码:

function fn1(fn){
$.ajax({
url:'',
success:funtion(data){
fn() && fn(data.n)
}
})
}
function fn2(n,fn){
$.ajax({
url:'',
data:{
n:n
},
success:funtion(data){
fn() && fn(data.n)
}
})
}
function fn3(n,fn){
$.ajax({
url:'',
data:{
n:n
},
success:funtion(data){
fn() && fn(data.n)
}
})
}
fn1(function(n1){
fn2(n1,function(n2){
fn3(n2)
})
})

同样的需求,通过回调函数的形式去解决就会方便很多,无论是代码的清晰度还是维护来说都会比较方便,即使这样是解决了一些问题,但仍然没有解决根本性的问题,还是需要通过回调函数去找到各个函数之间一一对应的关系,多多少少的也会带来一些小的困扰。

为了解决类似这样的问题,EcmaScript6的发布推出了Promise对象,Promise的推出无非是所有学习JavaScript人的一个福音,Promise主要就是为了结果异步操作和地狱式回调函数的问题。

从头到尾扯了了这么多,那么到底Promise到底是什么东西?主要能做什么?有什么特点?该怎么使用Promise对象?脑海中出现了一串的问号。

Promise是什么?

Promise简单的来说就是一个容器,里面保存着未来会结束的某个事件。一般来说会是一个异步操作。Promise可以获取到异步操作的消息。

Promise的特点?

  • 对象的状态不会受到外界的影响。Promise代表的是一个异步的操作。一共有三种状态pending(进行中),resolved(已做完)和rejected(已失败),异步操作的结果,决定这Promise最终的状态。成功的则会使用resolved作为回调,如果失败则会使用rejected作为回调,任何的操作都无法改变其中的状态。
  • Promise的状态一旦改变,就永远不会再改变。Promise的改变只有两种情况,Pending –> rejected或者Pending-> resolved,Promise的最大特点也就是在此,如果你错过了他的返回结果,如果再想通过某种方法,去获得Promise的返回结果是获取不到的。

Promise语法应用:

Promise是一个对象,需要使用new得到一个新的Promise对象。

代码示例:

function imgs (url){
return new Promise ((resolve, reject) => {
var oImg = new Image;
oImg.src = url;
oImg.onload = function(){
resolve(this);
}
oImg.onerror = function(){
reject(new Error("图片加载失败"))
}
})
}
var oImg = imgs("http://aaronblog.vip/www/img/banner/banner-1517467815031.jpg");
oImg.then((oimg) => {
console.log(oimg)
}).catch((err) => {
console.log(err)
})

先分析一下上面的代码,在imgs函数中return出去了一个Promise 对象,然而在Promise对象里面,使用new方法新建了一个Image对象,为这个Image对象添加了一个src,并执行性onload,和onerror方法,当图片加载完成后使用resolve方法,并把this(this指向的是Image)作为参数作为传了出去,当图片加载失败的时候使用reject函数,并new一个Error返回出去。在执行imgs函数之后通过oImg变量接收,此时oImg对象得到的就是一个Promise对象。

在Promise的原型上分别挂载着两个方法,then和catch,then代表成功,catch代表的是失败。所以在oImg.then的时候可以拿到传出来的Image对象。如果此时图片加载失败则会走catch,接收到的就是new Error的信息。

需要注意的是,在then函数中一共两个回调函数,第一个是成功,第二个是失败,同样可以捕获到错误

代码示例:

var oImg = imgs("http://aaronblog.vip/www/img/banner/banner-1517467815031.jpg");
oImg.then((oimg) => {
console.log(oimg)
},(err) => {
console.log(err)
})

但是我们一般情况下不会这样去做,都是使用catch去捕获错误。这一点很重要。如果你非要使用这种方法也是可以的。不会出现问题。

实例应用:

function $ajax(data){
return new Promise(resolve, reject){
$.ajax({
url:"",
data:data,
success:resolve,
error:reject
})
}
}
var P1 = $ajax({a:1});
P1.then((data) => {
console.log("我成功了!")
}).catch((err) => {
console.log("我失败了!")
})

`

上面ajax方法就是使用Promise对象进行了二次封装,当success的时候去使用resolve,error的时候则去执行reject方法,无论是失败还是成功,resolve,reject都可以接收到对应函数返回的结果。这样的话通过P1.then的方法就可以轻松的完成回调了。

刚才在上面也有提到过Promise对象上挂载着两个方法then和catch,所以then和catch可以连续调用。

代码示例:

var P1 = $ajax({a:1});
P1.then((data) => {
console.log("我成功了!")
}).catch((err) => {
console.log("我失败了!")
}).then(() => {
console.log("我执行了!")
}).then(() => {
console.log("我是最后执行!")
})

通过上面的代码如果Ajax请求成功,会依次打印“我成功了!”,“我执行了!”,“我是最后执行!”,从这里可以分析出,这里的then是一步一步执行的,而不是异步执行的。记住这一点很重要。

Promise实例的异步方法和then中返回的Promise的应用。

第一种情况

let p2 = new Promise ( ... )
let p1 = new Promise ( (resolve, reject) => {
resolve(p2)
} )

在p1中成功之后传入了p2,需要注意的是,p1中的resolve执行与不执行完全取决于p2的返回状态,如果p2返回的是成功,则p1.then会执行,否则是相反的。则p1则会走向catch方法。

第二种情况

let p3 = new Promise ( (resolve, reject) => {
resolve()
} )
let p4 = new Promise ( ... )
p3.then(
() => return p4
)

在p3执行了then方法之后又return出去了p4此时,如果在p3后面再去使用then方法的话则会指向p4的then,而不再指向p3。catch则会同属于p3和p4。这种情况一般应用于,几个方法使用同样的操作去调整错误信息。

以上是promise需要着重掌握的部分,下面在介绍一下关于promise的其他相关的API的使用

上面说过Promise原型上挂载了两个方法then和catch,分别用来接受成功和失败的状态,但是除了这两个方法以外,还有其他的方法,由于这些方法不经常使用就简单的介绍一下。

Promise其他API

Promise.resolve()/Promise.reject()

这两种方法会把一个对象封装成一个Promise对象,两者唯一不同的地方就是Promise.resolve()会根据参数的情况返回不同的Promise。

需要注意的是:

  • 如果参数是Promise对象的话,则会直接返回传入的Promise对象。
  • 参数带有then方法,转换成Promise对象之后立即执行then方法。
  • 参数不带then方法,不是对象或没有参数,返回的则是Promise的失败状态。

代码示例:

var p1 = Promise.resolve([1, 2, 3]);
p1.then(function(value) {
console.log(value);
//[1, 2, 3]
});

Promise.all

这个方法接收的是一个数组,可以接收Promise对象,如果传入的不是Promise对象则,会默认的调用Promise.resolve()将其转换成Promise对象。在all()方法后面可以使用then方法,去接收参数,then方法里面存放的是一个数组,与传入的Promise对象的顺序是一一对应的关系。没有返回值则是undefined。
需要注意的是在使用all方法的时候,如果传入的Promise对象,其中的任何一个失败了,则就会走向catch,将不会再等待其他Promise对象的返回结果。

代码示例:

var p1 = Promise.resolve(3);
var p2 = 42;
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p1, p2, p3]).then(function(values) {
console.log(values);
});
// expected output: Array [3, 42, "foo"]

上面代码p2不是一个promise对象,默认调用了Promise.resolve()转换成了promise对象,并返回了出去。

Promise.race

“比赛(竞速)”方法,这方法接收的同样也是一个数组,其传入的参数如果不是一个promise对象,则会调用Promise.resolve()方法,将其转换成Promise对象。

这个方法如果传入的数组中的Promise对象,哪个先接收到结果就走入then成功,函数如果全部都失败的,则会走向catch。

代码示例:

var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([p1, p2]).then(function(value) {
console.log(value);
//100
});

上面代码使用setTimeout模拟异步操作,由于p2的时间要比p1快很多所以输出的结果就是2了。

Promise.done

这个方法与Promise.then是类似的,同样可以提供resolved和rejected方法,也可以不提供任何的参数,其主要的目的是为了捕获then或catch尾端没有捕获到的错误。

Promise.finally

作为Promise的后续方法,无论是成功或是失败都会走这个方法。

结语

Promise对象可以高效的解决地狱式回调,使得代码变得更加清晰,易于维护。Promise虽然有他的好处,但是对于解决异步的解决方案,Es6/Es7还提出了其他的解决方案,以后有时间再详细的和大家说一下,最后感谢大家花费这么长时间阅读这篇文章。如果有什么异议,可以联系我或者在文章下方留言。

浅谈Promise的更多相关文章

  1. 浅谈Promise原理与应用

    在JavaScript中,所有代码都是单线程.由于该“缺陷”,JavaScript在处理网络操作.事件操作时都是需要进行异步执行的.AJAX就是一个典型的异步操作 对于异步操作,有传统的利用回调函数和 ...

  2. 浅谈Angular的 $q, defer, promise

    浅谈Angular的 $q, defer, promise 时间 2016-01-13 00:28:00  博客园-原创精华区 原文  http://www.cnblogs.com/big-snow/ ...

  3. 浅谈ES6原生Promise

    浅谈ES6原生Promise 转载 作者:samchowgo 链接:https://segmentfault.com/a/1190000006708151 ES6标准出炉之前,一个幽灵,回调的幽灵,游 ...

  4. 浅谈HTML5单页面架构(一)——requirejs + angular + angular-route

    心血来潮,打算结合实际开发的经验,浅谈一下HTML5单页面App或网页的架构. 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又要数单页面架构体验 ...

  5. AngularJS进阶(二十五)requirejs + angular + angular-route 浅谈HTML5单页面架构

    requirejs + angular + angular-route 浅谈HTML5单页面架构 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又 ...

  6. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  7. 《浅谈我眼中的express、koa和koa2》好文留存+笔记

    原文 :三英战豪强,思绪走四方.浅谈我眼中的express.koa和koa2 一.回调大坑怎么解决呢? 1.es5可以利用一下第三方库,例如 async 库, 2.或者单纯使用 connect中间件  ...

  8. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  9. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

随机推荐

  1. CEF3可行性

    Chromium Embedded Framework 顾名思义,内嵌式CHROME,详细的介绍参阅 http://yogurtcat.com/posts/cef/hello-cef.html 为什么 ...

  2. Python中list的实现

    原文链接这篇文章介绍了Python中list是如何实现的.在Python中list特别有用.让我们来看下list的内部是如何实现的.来看下面简单的程序,在list中添加一些整数并将他们打印出来. &g ...

  3. 转:MediaCoder H.264格式编码参数设置及详解

    转: http://mediacoder.com.cn/node/81 由于现在大部分视频转码都选择H.264格式进行编码,同时CUDA编码的画质还达不到x264软编码的质量(如果你对画质无要求,可以 ...

  4. VS2010恢复默认编辑环境的设置

    VS2010恢复默认编辑环境的设置 VS2010在安装完成后初次打开的时候可以设置自己常用的环境为默认打开的编辑环境, 也可以在打开IDE以后通过如下步骤设置默认环境: Tools->Impor ...

  5. Angular随笔第一课

    一.调用angular 加载angular.js库(可以从google的cdn中加载类库,https://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/a ...

  6. 天梯赛 L3-013 非常弹的球 找规律

    L3-013. 非常弹的球 时间限制 100 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 俞勇(上海交通大学) 刚上高一的森森为了学好物理,买了一个 ...

  7. nslookup命令的安装及使用

    windows中是自带的,不用安装,直接在cmd窗口直接使用 Linux中需要安装: yum -y install bind-utils nslookup www.baidu.com [root@bo ...

  8. selenium-webdriver循环点击百度搜索结果以及获取新页面的handler

    webdriver还是很有意思的,之前用过Ruby的watir的自动化测试框架,感觉selenium的这套框架更好一些,很容易就可以上手.我虽然不做自动化这块,不过先玩玩再说,多学点东西总之还是好一些 ...

  9. javascript 零碎笔记

    使用 live-serve 这个工具,可以热更新 js 代码 逻辑运算符: 常用于单边条件判断,比如 真判断(获取子属性) {error && <div className=&q ...

  10. 点击iframe窗口里的超链接,打开新页面的方式

    点击iframe窗口里的超链接打开新页面的方式: a标签中设置按钮点击事件,事件调用的方法使用如下方法跳转链接:  window.open('url链接', '_blank');