JavaScript异步编程(1)- ECMAScript 6的Promise对象
JavaScript的Callback机制深入人心。而ECMAScript的世界同样充斥的各种异步操作(异步IO、setTimeout等)。异步和Callback的搭载很容易就衍生"回调金字塔"。——由此产生Deferred/Promise。
Deferred起源于Python,后来被CommonJS挖掘并发扬光大,得到了大名鼎鼎的Promise,并且已经纳入ECMAScript 6(JavaScript下一版本)。
Promise/Deferred是当今最著名的异步模型,不仅强壮了JavaScript Event Loop(事件轮询)机制下异步代码的模型,同时增强了异步代码的可靠性。—— 匠者为之,以惠匠者。
> 本文内容如下:
>
> - Promise应对的问题
> - Promise的解决
> - ECMAScript 6 Promise
> - 参考和引用
Promise应对的问题
JavaScript充斥着Callback,例如下面的代码:
(function (num) {//从外面接收一个参数
var writeName = function (callback) {
if (num === 1)
callback();
}
writeName(function () {//callback
console.log("i'm linkFly");
});
})(1);
把一个函数通过参数传递,那么这个函数叫做Callback(回调函数)。
JavaScript也充斥着异步操作——例如ajax。下面的代码就是一段异步操作:
var name;
setTimeout(function () {
name = 'linkFly';
}, 1000);//1s后执行
console.log(name);//输出undefined
这段代码的运行逻辑是这样的:

我们的总是遇见这样的情况:一段代码异步执行,后续的代码却需要等待异步代码的,如果在异步代码之前执行,就会如上面的console.log(name)一样,输出undefined,这并不是我们想要的效果。
类似的情况总是发生在我们经常要使用的ajax上:
$.ajax({
url: 'http://www.cnblogs.com/silin6/map',
success: function (key) {
//我们必须要等待这个ajax加载完成才能发起第二个ajax
$.ajax({
url: 'http://www.cnblogs.com/silin6/source/' + key,
success: function (data) {
console.log("i'm linkFly");//后输出
}
});
}
});
console.log('ok');//ok会在ajax之前执行
异步操作有点类似这一段代码被挂起,先执行后续的代码,直到异步得到响应(例如setTimeout要求的1s之后执行,ajax的服务器响应),这一段异步的代码才会执行。关于这一段异步代码的执行流程,请参阅JavaScript大名鼎鼎的:Event Loop(事件轮询)。
Promise的解决
Promise优雅的修正了异步代码,我们使用Promise重写我们setTimeout的示例:
var name,
p = new Promise(function (resolve) {
setTimeout(function () {//异步回调
resolve();
}, 1000);//1s后执行
});
p.then(function () {
name = 'linkFly';
console.log(name);//linkFly
}).then(function () {
name = 'cnBlog';
console.log(name);
});
//这段代码1s后会输出linkFly,cbBlog
我们先不要太过在意Promise对象的API,后续会讲解,我们只需要知道这段代码完成了和之前同样的工作。我们的console.log(name)正确的输出了linkFly,并且我们还神奇的输出了cnBlog。
或许你觉得这段代码实在繁琐,还不如setTimeout来的痛快,那么我们再来改写上面的ajax:
var ajax = function (url) {
//我们改写ajax,让它以Promise的方式工作
return new Promise(function (resolve) {
$.ajax({
url: url,
success: function (data) {
resolve(data);
}
});
});
};
ajax('http://www.cnblogs.com/silin6/map')
.then(function (key) {
//我们得到key,发起第二条请求
return ajax('http://www.cnblogs.com/silin6/source/' + key);
})
.then(function (data) {
console.log(data);//这时候我们会接收到第二次ajax返回的数据
});
或许它晦涩难懂,那么我们尝试用setTimeout来模拟这次的ajax,这个例子演示了Promise数据的传递,一如ajax:
var name,
ajax = function (data) {
return new Promise(function (resolve) {
setTimeout(function () {//我们使用setTimeout模拟ajax
resolve(data);
}, 1000);//1s后执行
});
};
ajax('linkFly').then(function (name) {
return ajax("i'm " + name);//模拟第二次ajax
}).then(function (value) {
//2s后,输出i'm linkFly
console.log(value);
});
上面的代码,从代码语义上达到了下面的流程:

我们仅观察代码就知道现在的它变得非常优雅,两次异步的代码被完美的抹平。但我们应该时刻谨记,Promise改变的是你异步的代码和编程思想,而并没有改变异步代码的执行——它是一种由卓越的编程思想所衍生的对象。
下面一张图演示了普通异步回调和Promise异步的区别,Promise实现的异步从代码运行上来说并无太大区别,但从编程思想上来说差异巨大。

ECMAScript 6 Promise##
Promise对象代表了未来某个将要发生的事件(通常是一个异步操作),抹平了异步代码的金字塔,它从模型上解决了异步代码产生的"回调金字塔"。
Promise是ECMAScript 6规范内定义的,所以请使用现代浏览器测试,它的兼容性可以在这里查看。
Promise.constructor
Promise是一个对象,它的构造函数接收一个回调函数,这个回调函数参数有两个函数:分别在成功状态下执行和失败状态下执行,Promise有三个状态,分别为:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
var p = new Promise(function (resolve,reject) {
console.log(arguments);
//resolve表示成功状态下执行
//reject表示失败状态下执行
});
传递的这个回调函数,等同被Promise重新封装,并传递了两个参数回调,这两个参数用于驱动Promise数据的传递。resolve和reject本身承载着触发器的使命:
- 默认的Promise对象是等待态(Pending)。
- 调用resolve()表示这个Promise进入执行态(Fulfilled)
- 调用reject()表示这个promise()进入拒绝态(Rejected)
- Promise对象可以从等待状态下进入到执行态和拒绝态,并且无法回退。
- 而执行态和拒绝态不允许互相转换(例如执行态转换到拒绝态)。
Promise.prototype.then
生成的promise实例(如上面的变量p)拥有方法then(),then()方法是Promise对象的核心,它返回一个新的Promise对象,因此可以像jQuery一样链式操作,非常优雅。
Promise是双链的,所以then()方法接受两个参数,分别表示:
- _执行态(Fulfilled)_下执行的回调函数
- _拒绝态(Rejected)_下执行的回调函数。
p.then(function () {
//我们返回一个promise
return new Promise(function (resolve) {
setTimeout(function () {
resolve('resolve');
}, 1000);//异步1s
});
}, function () {
console.log('rejected');
}) //链式回调
.then(function (state) {
console.log(state);//如果为执行态,输出resolve
}, function (data) {
console.log(data);//如果为拒绝态,输出undefined
});;
then()方法的返回值由它相应状态下执行的函数决定:这个函数返回undefined,则then()方法构建一个默认的Promise对象,并且这个对象拥有then()方法所属的Promise对象的状态。
var p = new Promise(function (resolve) {
resolve();//直接标志执行态
}), temp;
temp = p.then(function () {
//传入执行态函数,不返回值
});
temp.then(function () {
console.log('fulfilled');//拥有p的状态
});
console.log(temp === p);//默认构建的promise,但已经和p不是同一个对象,输出false
如果对应状态所执行的函数返回一个全新的Promise对象,则会覆盖掉当前Promise,代码如下:
var p = new Promise(function (resolve) {
resolve();//直接标志执行态
}), temp;
temp = p.then(function () {
//返回新的promise对象,和p的状态无关
return new Promise(function (resolve, reject) {
reject();//标志拒绝态
});
});
temp.then(function () {
console.log('fulfilled');
}, function () {
console.log('rejected');//输出
});
即then()方法传递的进入的回调函数,如果返回promise对象,则then()方法返回这个promise对象,否则将默认构建一个新的promise对象,并继承调用then()方法的promise的状态。
我们应该清楚Promise的使命,抹平了异步代码的回调金字塔,我们会有很多依赖上一层异步的代码:
var url = 'http://www.cnblogs.com/silin6/';
ajax(url, function (data) {
ajax(url + data, function (data2) {
ajax(url + data2, function (data3) {
ajax(url + data3, function () {
//回调金字塔
});
});
});
});
使用Promise则抹平了代码:
promise.then(function (data) {
return ajax(url + data);
}).then(function (data2) {
return ajax(url + data2);
}).then(function (data3) {
return ajax(url + data3);
}).then(function (data) {
//扁平化代码
});
Promise还有更多更强大的API。但本文的目的旨在让大家感受到Promise的魅力,而并非讲解Promise对象自身的API,关于Promise其他辅助实现API请查阅本文最下方的引用章节,Promise其他API如下:
- Promise.prototype.catch():用于指定发生错误时的回调函数(捕获异常),并具有冒泡性质。
- Promise.all(),Promise.race():Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
- Promise.resolve(),Promise.reject():将现有对象转为Promise对象。
希望大家一点点的接受Promise,所以没有讲太多,我们对于Promise的理解不应该仅仅是一个异步模型,我们更关注应该是Promise/Deferred的编程思想,所以后续几篇会逐渐深入讲解Promise的前生今世。
参考和引用##
JavaScript异步编程(1)- ECMAScript 6的Promise对象的更多相关文章
- JavaScript 异步编程(二):Promise
PromiseState Promise 有一个 [[PromiseState]] 属性,表示当前的状态,状态有 pending 和 fulfill 以及 reject. 从第一个 Promise 开 ...
- javascript异步编程,promise概念
javascript 异步编程 概述 采用单线程模式工作的原因: 避免多线dom操作同步问题,javascript的执行环境中负责执行代码的线程只有一个 内容概要 同步模式和异步模式 事件循环和消息队 ...
- JavaScript异步编程原理
众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着. ...
- JavaScript异步编程(2)- 先驱者:jsDeferred
JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范. 研究javascri ...
- JavaScript异步编程的主要解决方案—对不起,我和你不在同一个频率上
众所周知(这也忒夸张了吧?),Javascript通过事件驱动机制,在单线程模型下,以异步的形式来实现非阻塞的IO操作.这种模式使得JavaScript在处理事务时非常高效,但这带来了很多问题,比如异 ...
- javascript异步编程的前世今生,从onclick到await/async
javascript与异步编程 为了避免资源管理等复杂性的问题, javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是为 ...
- Promises与Javascript异步编程
Promises与Javascript异步编程 转载:http://www.zawaliang.com/2013/08/399.html 在如今都追求用户体验的时代,Ajax应用真的是无所不在.加上这 ...
- 5分种让你了解javascript异步编程的前世今生,从onclick到await/async
javascript与异步编程 为了避免资源管理等复杂性的问题,javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是 ...
- 转: Promises与Javascript异步编程
在如今都追求用户体验的时代,Ajax应用真的是无所不在.加上这些年浏览器技术.HTML5以及CSS3等的发展,越来越多的富Web应用出现:在给与我们良好体验的同时,Web开发人员在背后需要处理越来越多 ...
随机推荐
- bootstrap中的Grid system详解
啦啦啦,都十月中旬啦,好快啊,这个月的多一半都过去了,然而我才写第三篇随笔,小颖得加油啦~~~ 下面来看下小颖给大家分享的内容 1. .col-md-*和.col-xs-* <!doctyp ...
- jQuery UI Datepicker使用介绍
本博客使用Markdown编辑器编写 在企业级web开发过程中,日历控件和图表控件是使用最多的2中第三方组件.jQuery UI带的Datepicker,日历控件能满足大多数场景开发需要.本文就主要讨 ...
- CodeIgniter笔记-去掉index.php
1.修改Apache 的配置文件把LoadModule rewrite_module modules/mod_rewrite.so该行前的#去掉. 2.将 AllowOverride None ,修改 ...
- iOS开发——高级特性&Runtime运行时特性详解
Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 ...
- 1Z0-053 争议题目解析681
1Z0-053 争议题目解析681 考试科目:1Z0-053 题库版本:V13.02 题库中原题为: 681.The environmental variable ORACLE-BASE is set ...
- what's this? 浅谈js中this的指向问题
刚刚学习js的朋友可能和我一样,看到代码中的this总是一脸懵逼,不知道this到底指向谁.经过一段时间的了解,我想跟大家分享下自己的理解. 何时出现this 函数在调用的时候,会自动获得两个特殊变量 ...
- Apworks框架实战(三):单元测试与持续集成
虽然这部分内容并没有过多地讨论Apworks框架的使用,但这部分内容非常重要,它与Apworks框架本身的设计紧密相关,也是进一步了解Apworks框架设计的必修课. 单元测试与持续集成概述 在敏捷开 ...
- Struts2 源码分析——调结者(Dispatcher)之执行action
章节简言 上一章笔者写关于Dispatcher类如何处理接受来的request请求.当然读者们也知道他并非正真的执行action操作.他只是在执行action操作之前的准备工作.那么谁才是正真的执行a ...
- 7.11 数据注解特性--InverseProperty
我们已经知道了,Code--First默认的约定,如果你没有包含外键属性在父类中,那么他会为我们创建{Class Name}_{primary Key}外键.这个InverseProperty特性用在 ...
- C#开发微信门户及应用(42)--使用Autofac实现微信接口处理的控制反转处理
在很多情况下,我们利用IOC控制反转可以很方便实现一些接口的适配处理,可以在需要的时候切换不同的接口实现,使用这种方式在调用的时候,只需要知道相应的接口接口,具体调用哪个实现类,可以在配置文件中动态指 ...