一、前言

JavaScript是单线程的,固,一次只能执行一个任务,当有一个任务耗时很长时,后面的任务就必须等待。那么,有什么办法,可以解决这类问题呢?(抛开WebWorker不谈),那就是让代码异步执行嘛。什么意思,如Ajax异步请求时,就是通过不断监听readyState的值,以确定执行指定的回调函数。

通常的异步执行有三种,回调函数、事件监听以及发布订阅,其中事件监听和发布订阅其实差不多,只是后者更加健壮一些。

如回调函数,回调函数是应用在异步执行中最简单的编程思想。如下:

function async(item,callback){
console.log(item);
setTimeout(function(){
callback(item+1);
},1000);
}

在上述列子中,执行async函数时,完成打印操作,并在1秒后执行callback回调函数(但不一定是1秒,详情见”setTimeout那些事儿”)。

异步的主要目的就是处理非阻塞,提升性能。想象一下,如果某个操作需要经过多个async函数操作呢,如下:

async(1, function(item){
async(item, function(item){
async(item, function(item){
console.log('To be continued..');
});
});
});

是不是有点不易阅读了?

再比如,为了让上述代码更加健壮,我们可以加入异常捕获。在异步的方式下,异常处理分布在不同的回调函数中,我们无法在调用的时候通过try…catch的方式来处理异常, 所以很难做到有效,清楚。

哎哟喂,那可怎么办呢?

噔噔噔噔,噔噔噔噔—Promise闪亮登场。

倘若用ES6的Promise优化上述代码,可得:

function opration(item){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
resolve(item+1);
},1000);
});
console.log(item);
return p;
}
function failed(e){
console.log(e);
}
Promise.resolve(1).then(opration).then(opration).then(opration).catch(failed);

Promise 优化后的代码,优点显而易见,让回调函数变成了链式调用,避免了层层嵌套,使程序流程变得清晰明朗,并为一个或者多个回调函数抛出的错误通过catch方法进行统一处理。

哎呦,不错嘛,那这个ES6中的Promise到底是何方圣神,具体使用法则是什么呢?我们就一起来探究探究。

该篇博客原文地址:http://www.cnblogs.com/giggle/p/5575157.html

二、Promise概述

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

Promise对象有且只有三种状态:

  1、 pending:异步操作未完成。

  2、 resolved:异步操作已完成。

  3、 rejected:异步操作失败。

又,这三种状态的变化只有两种模式,并且一旦状态改变,就不会再变:

  1、异步操作从pending到resolved;

  2、异步操作从pending到rejected;

好了,既然它是属于ES6规范,我们再通过chrome,直接打印出Promise,看看这玩意:

恩,一目了然,Promise为构造函数,欧克,这样通过它,我们就可以实例化自己的Promise对象了,并加以利用。

三、Promise入门指南

Promise既然是一个构造函数,那么我们就先new一个看看。如下:

var p = new Promise();

并执行上述代码,chrome截图如下:

怎么报错了呢?

哦,对了,Promise构造函数,需要一个函数作为其参数哦,并且作为参数的函数中,有两个参数,第一个参数的作用为,当异步操作从pending到resolved时,供其调用;第二个参数的作用为,当异步操作从pending到rejected时,供其调用。

例如,我将匿名函数的第一个参数取名为resolve;第二个参数取名为reject。Demo如下:

var p = new Promise(function(resolve, reject){
console.log('new一个Promise对象');
setTimeout(function(){
resolve('Monkey');
},1000);
});

并执行上述代码,chrome截图如下:

特别提醒:当传入匿名函数作为构造函数Promise的参数时,我们在new的时候,匿名函数就已经执行了,如上图。

咦,上述代码中,我们在匿名函数中,通过setTimeout定时器,在1秒后,还调用了resolve呢,怎么没有报undefined或者错误呢?!

这就是Promise强大之处的一点。正因为这样,我们就可以将异步操作改写成优雅的链式调用。怎么调用呢?

还记得,我们在“Promise概述”一小节中,通过chrome打印Promise,用红线框中的区域么?其中,Promise原型中有一then方法(Promise.prototype.then),通过这个then方法,就可以了。如下:

p.then(function(value){
console.log(value);
});

其中,then方法有两个匿名函数作为其参数,与Promise的resolve和reject参数一一对应。执行代码,结果如下:

好了,当then执行完后,如果我们想继续在其之后看,使用then方法链式调用,有两种情况,一种是直接返回非Promise对象的结果;另一种是返回Promise对象的结果。

1、返回非Promise对象的结果:紧跟着的then方法,resolve立刻执行。并可使用前一个then方法返回的结果。如下:

p.then(function(value){
console.log(value);
//返回非Promise对象,如我的对象
return {
name: 'Dorie',
age: 18
};
}).then(function(obj){
console.log(obj.name);
});

执行上述完整代码,chrome截图如下:

2、返回Promise对象的结果:紧跟着的then方法,与new Promise后的then方法一样,需等待前面的异步执行完后,resolve方可被执行。如下:

p.then(function(value){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
var message = value + ' V Dorie'
resolve(message);
},1000);
});
console.log(value);
//返回一个Promise对象
return p;
}).then(function(value){
console.log(value);
});

执行上述完整代码,chrome截图如下:

那么,当创建、执行Promise方法中有异常报错,如何捕获呢?

Promise.prototype.catch原型方法,就是为其而设定的。它具有冒泡的特性,比如当创建Promise实例时,就出错了,错误消息就会通过链式调用的这条链,一直追溯到catch方法,如果找到尽头都没有,就报错,并且再找到catch之前的所有then方法都不能执行了。Demo如下(代码太长,请自行展开):

<!DOCTYPE html>
<head>
<title>test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
var p = new Promise(function(resolve, reject){
//M未定义
console.log(M);
setTimeout(function(){
resolve('Monkey');
},1000);
});
p.then(function(value){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
var message = value + ' V Dorie'
resolve(message);
},1000);
});
console.log(value);
//返回一个Promise对象
return p;
}).then(function(value){
console.log(value);
return 'next is catch';
}).catch(function(e){
console.log(e);
}).then(function(value){
console.log('execute,but value is ' + value);
});
</script>
</body>
</html>

执行上述代码,chrome截图如下:

好了,到这里,我们已经了解了最常用的Promise.prototype.then和Promise.prototype.catch这两个原型方法。另外,像Promise构造函数还有属于自身的方法,如all、rece、resolve、reject等,详情请点击这里(here)。

通过一路上对Promise的讲述,我们也有了一定的认识,其实Promise并没有想象中的那么难以理解嘛。懂得Promise概念后,其实我们自己也可以实现一个简易版的Promise。下面就一同尝试实现一个呗。

四、模拟Promise

假设:有三个异步操作方法f1,f2,f3,且f2依赖于f1,f3依赖于f2。如果,我们采用ES6中Promise链式调用的思想,我们可以将程序编写成这样:

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

那么,通过上面这一系列链式调用,怎样才能达到与ES6中Promise相似的功能呢?

初步想法:首先将上述链式调用的f2、f3保存到f1中,当f1中的异步执行完后,再调用执行f2,并将f1中的f3保存到f2中,最后,等f2中的异步执行完毕后,调用执行f3。详细构思图,如下:

从上图可知,由于f1、f2 、f3是可变得,所以存储数组队列thens,可放入,我们即将创建的模拟Promise构造函数中。具体实现代码如下:

//模拟Promise
function Promise(){
this.thens = [];
};
Promise.prototype = {
constructor: Promise,
then: function(callback){
this.thens.push(callback);
return this;
}
};

并且,需要一个Promise.prototype.resolve原型方法,来实现:当f1异步执行完后,执行紧接着f1后then中的f2方法,并将后续then中方法,嫁接到f2中,如f3。具体实现代码如下:

//模拟Promise,增加resolve原型方法
function Promise(){
this.thens = [];
};
Promise.prototype = {
constructor: Promise,
then: function(callback){
this.thens.push(callback);
return this;
},
resolve: function(){
var t = this.thens.shift(),
p;
if(t){
p = t.apply(null,arguments);
if(p instanceof Promise){
p.thens = this.thens;
}
}
}
};

测试代码(代码太长,自行打开并运行)。

function f1() {
var promise = new Promise();
setTimeout(function () { console.log(1);
promise.resolve();
}, 1500) return promise;
} function f2() {
var promise = new Promise();
setTimeout(function () {
console.log(2);
promise.resolve();
}, 1500);
return promise;
} function f3() {
var promise = new Promise();
setTimeout(function () { console.log(3);
promise.resolve();
}, 1500) return promise;
}
f1().then(f2).then(f3);

测试代码

仔细品味,上述实现的Promise.prototype.resolve方法还不够完美,因为它只能够满足于f1、f2、f3等方法都是使用模拟的Promise异步执行的情况。而,当其中有不是返回的Promise对象的呢,而是返回一个数字,亦或是什么也不返回,该怎么办?所以,针对以上提出的种种可能,再次改进resolve。改善代码如下:

//模拟Promise,改善resolve原型方法
var Promise = function () {
this.thens = [];
};
Promise.prototype = {
constructor: Promise,
then: function(callback){
this.thens.push(callback);
return this;
},
resolve: function () {
var t,p;
t = this.thens.shift();
t && (p = t.apply(null, arguments));
while(t && !(p instanceof Promise)){
t = this.thens.shift();
t && (p = t.call(null, p));
}
if(this.thens.length){
p.thens = this.thens;
};
}
}

测试代码(代码太长,自行打开并运行)。

function f1() {
var promise = new Promise();
setTimeout(function () { console.log(1);
promise.resolve();
}, 1500) return promise;
} function f2() {
var promise = new Promise();
setTimeout(function () {
console.log(2);
promise.resolve();
}, 1500);
return promise;
} function f3() {
var promise = new Promise();
setTimeout(function () { console.log(3);
promise.resolve();
}, 1500) return promise;
} function f4() {
console.log(4);
return 11;
} function f5(x) {
console.log(x+1);
} function f6() {
var promise = new Promise();
setTimeout(function () { console.log(6);
promise.resolve();
}, 1500) return promise;
} function f7() {
console.log(7);
} var that = f1().then(f2).then(f3).then(f4).then(f5).then(f6).then(f7);

测试代码

好了,初步模拟的Promise就OK啦。

吼吼,对于Promise,我们这一路走来,发现原来也不过如此呢。

五、拓展阅读

[1]、再谈Event Loop

[2]、MDN

[3]、大白话讲解Promise(一)

[4]、当耐特

[5]、Promise & Deferred objects in JavaScript Pt.1

细说Promise的更多相关文章

  1. Promise 用法

    Promise是一个构造函数,自己身上有all.reject.resolve这几个眼熟的方法,原型上有then.catch等同样很眼熟的方法. 那就new一个 Promise的构造函数接收一个参数,是 ...

  2. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  3. 【原创】ReFlux细说

    ReFlux细说 Flux作为一种应用架构(application architecture)或是设计模式(pattern),阐述的是单向数据流(a unidirectional data flow) ...

  4. 【JavaScript】JavaScript Promise 探微

    http://www.html-js.com/article/Promise-translation-JavaScript-Promise-devil-details 原文链接:JavaScript ...

  5. 比callback更简洁的链式执行promise

    promise自己理解的也不够深刻,具体知识点不在这里细说了 直接上个例子,清晰明了,自己去悟吧 <script type="text/javascript"> //模 ...

  6. ES6 之 let和const命令 Symbol Promise对象

    ECMAScript 6入门 ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了. (2016年6月,发布了小幅修订的<ECMASc ...

  7. Q promise API简单翻译

    详细API:https://github.com/kriskowal/q/wiki/API-Reference Q提供了promise的一种实现方式,现在在node中用的已经比较多了.因为没有中文的a ...

  8. ArcGIS API for JavaScript 4.2学习笔记[7] 鹰眼(缩略图的实现及异步处理、Promise、回调函数、监听的笔记)

    文前说明:关于style就是页面的css暂时不做评论,因为官方给的例子的样式实在太简单了,照抄阅读即可. 这篇文章有着大量AJS 4.x版本添加的内容,如监听watch.Promise对象.回调函数. ...

  9. 细说并发编程-TPL

    本节导航 基本概念 并发编程 TPL 线程基础 windows为什么要支持线程 线程开销 CPU的发展 使用线程的理由 如何写一个简单Parallel.For循环 数据并行 Parallel.For剖 ...

随机推荐

  1. myeclipse学习总结一(在MyEclipse中设置生成jsp页面时默认编码为utf-8编码)

    1.每次我们在MyEclispe中创建Jsp页面,生成的Jsp页面的默认编码是"ISO-8859-1".在这种情况下,当我们在页面中编写的内容存在中文的时候,就无法进行保存.如下图 ...

  2. SQL Server常见数据类型介绍

    数据表是由多个列组成,创建表时必须明确每个列的数据类型,以下列举SQL Server常见数据类型的使用规则,方便查阅. 1.整数类型 int 存储范围是-2,147,483,648到2,147,483 ...

  3. 再谈C#采集,一个绕过高强度安全验证的采集方案?方案很Low,慎入

    说起采集,其实我是个外行,以前拔过阿里巴巴的客户数据,在我博客的文章:C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子) 中,介绍过采集用的工具,其实很Low的,分析 ...

  4. Node.js:dgram模块实现UDP通信

    1.什么是UDP? 这里简单介绍下,UDP,即用户数据报协议,一种面向无连接的传输层协议,提供不可靠的消息传送服务.UDP协议使用端口号为不同的应用保留其各自的数据传输通道,这一点非常重要.与TCP相 ...

  5. UWP开发之Mvvmlight实践六:MissingMetadataException解决办法(.Net Native下Default.rd.xml配置问题)

    最近完成一款UWP应用,在手机端测试发布版(Release)的时候应用莫名奇妙的强行关闭,而同样的应用包在PC端一点问题都没有,而且Debug版在两个平台都没有问题,唯独手机的Release版有问题. ...

  6. 初步认识TDD

    TDD,测试驱动开发(Test Driven Development)是极限编程中倡导的程序开发方法,以其倡导先写测试程序,然后编码实现其功能得名.本文将对TDD有一个较为系统的认识.    基础属性 ...

  7. 微信小程序初探

    做为码农相信大家最近肯定都会听到微信小程序,虽然现阶段还没有正式开放注册,但大家可以还是可以开发测试. 到微信的WIKI(http://mp.weixin.qq.com/wiki?t=resource ...

  8. thinkphp数据的查询和截取

    public function NewsList(){ $this->assign('title','news'); $p = I('page',1); $listRows = 6; $News ...

  9. android 事件分发机制详解(OnTouchListener,OnClick)

    昨天做东西做到触摸事件冲突,以前也经常碰到事件冲突,想到要研究一下Android的事件冲突机制,于是从昨天开始到今天整整一天时间都要了解这方面的知识,这才懂了安卓的触摸和点击事件的机制.探究如下: 首 ...

  10. 新手学习web遇到的一些乱码问题

    在新手学习web网站学习的时候经常会遇到?????这种乱码,对于刚起步的菜鸟来说真的很头痛,很容易打击继续学的信心当然了对于菜鸟的我最近也遇到过乱码问题,沉浸其中不能自拔,爱的深啊!!!!!我所遇到的 ...