Promise 对象 异步 then 回调函数
Promise 对象
概述
Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise 可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。
注意,本章只是 Promise 对象的简单介绍。为了避免与后续教程的重复,更完整的介绍请看《ES6 标准入门》的《Promise 对象》一章。
首先,Promise 是一个对象,也是一个构造函数。
function f1(resolve, reject) {// 异步代码...
}var p1 = new Promise(f1);
上面代码中,Promise
构造函数接受一个回调函数f1
作为参数,f1
里面是异步操作的代码。然后,返回的p1
就是一个 Promise 实例。
Promise 的设计思想是,所有异步任务都返回一个 Promise 实例。Promise 实例有一个then
方法,用来指定下一步的回调函数。
var p1 = new Promise(f1);
p1.then(f2);
上面代码中,f1
的异步操作执行完成,就会执行f2
。
传统的写法可能需要把f2
作为回调函数传入f1
,比如写成f1(f2)
,异步操作完成后,在f1
内部调用f2
。Promise 使得f1
和f2
变成了链式写法。不仅改善了可读性,而且对于多层嵌套的回调函数尤其方便。
// 传统写法
step1(function (value1) {step2(value1, function(value2) {step3(value2, function(value3) {step4(value3, function(value4) {// ...});});});
});// Promise 的写法
(new Promise(step1)).then(step2).then(step3).then(step4);
从上面代码可以看到,采用 Promises 以后,程序流程变得非常清楚,十分易读。注意,为了便于理解,上面代码的Promise
实例的生成格式,做了简化,真正的语法请参照下文。
总的来说,传统的回调函数写法使得代码混成一团,变得横向发展而不是向下发展。Promise 就是解决这个问题,使得异步流程可以写成同步流程。
Promise 原本只是社区提出的一个构想,一些函数库率先实现了这个功能。ECMAScript 6 将其写入语言标准,目前 JavaScript 原生支持 Promise 对象。
Promise 对象的状态
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。
- 异步操作未完成(pending)
- 异步操作成功(fulfilled)
- 异步操作失败(rejected)
上面三种状态里面,fulfilled
和rejected
合在一起称为resolved
(已定型)。
这三种的状态的变化途径只有两种。
- 从“未完成”到“成功”
- 从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种。
- 异步操作成功,Promise 实例传回一个值(value),状态变为
fulfilled
。 - 异步操作失败,Promise 实例抛出一个错误(error),状态变为
rejected
。
Promise 构造函数
JavaScript 提供原生的Promise
构造函数,用来生成 Promise 实例。
var promise = new Promise(function (resolve, reject) {// ...if (/* 异步操作成功 */){resolve(value);} else { /* 异步操作失败 */reject(new Error());}
});
上面代码中,Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己实现。
resolve
函数的作用是,将Promise
实例的状态从“未完成”变为“成功”(即从pending
变为fulfilled
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。reject
函数的作用是,将Promise
实例的状态从“未完成”变为“失败”(即从pending
变为rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
下面是一个例子。
function timeout(ms) {return new Promise((resolve, reject) => {setTimeout(resolve, ms, 'done');});
}timeout(100)
上面代码中,timeout(100)
返回一个 Promise 实例。100毫秒以后,该实例的状态会变为fulfilled
。
Promise.prototype.then()
Promise 实例的then
方法,用来添加回调函数。
then
方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled
状态)时的回调函数,第二个是异步操作失败(变为rejected
)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。
var p1 = new Promise(function (resolve, reject) {resolve('成功');
});
p1.then(console.log, console.error);
// "成功"var p2 = new Promise(function (resolve, reject) {reject(new Error('失败'));
});
p2.then(console.log, console.error);
// Error: 失败
上面代码中,p1
和p2
都是Promise 实例,它们的then
方法绑定两个回调函数:成功时的回调函数console.log
,失败时的回调函数console.error
(可以省略)。p1
的状态变为成功,p2
的状态变为失败,对应的回调函数会收到异步操作传回的值,然后在控制台输出。
then
方法可以链式使用。
p1.then(step1).then(step2).then(step3).then(console.log,console.error);
上面代码中,p1
后面有四个then
,意味依次有四个回调函数。只要前一步的状态变为fulfilled
,就会依次执行紧跟在后面的回调函数。
最后一个then
方法,回调函数是console.log
和console.error
,用法上有一点重要的区别。console.log
只显示step3
的返回值,而console.error
可以显示p1
、step1
、step2
、step3
之中任意一个发生的错误。举例来说,如果step1
的状态变为rejected
,那么step2
和step3
都不会执行了(因为它们是resolved
的回调函数)。Promise 开始寻找,接下来第一个为rejected
的回调函数,在上面代码中是console.error
。这就是说,Promise 对象的报错具有传递性。
then() 用法辨析
Promise 的用法,简单说就是一句话:使用then
方法添加回调函数。但是,不同的写法有一些细微的差别,请看下面四种写法,它们的差别在哪里?
// 写法一
f1().then(function () {return f2();
});// 写法二
f1().then(function () {f2();
});// 写法三
f1().then(f2());// 写法四
f1().then(f2);
为了便于讲解,下面这四种写法都再用then
方法接一个回调函数f3
。写法一的f3
回调函数的参数,是f2
函数的运行结果。
f1().then(function () {return f2();
}).then(f3);
写法二的f3
回调函数的参数是undefined
。
f1().then(function () {f2();return;
}).then(f3);
写法三的f3
回调函数的参数,是f2
函数返回的函数的运行结果。
f1().then(f2()).then(f3);
写法四与写法一只有一个差别,那就是f2
会接收到f1()
返回的结果。
f1().then(f2).then(f3);
Promise 的实例
加载图片
我们可以把图片的加载写成一个Promise
对象。
var preloadImage = function (path) {return new Promise(function (resolve, reject) {var image = new Image();image.onload = resolve;image.onerror = reject;image.src = path;});
};
Ajax 操作
Ajax 操作是典型的异步操作,传统上往往写成下面这样。
function search(term, onload, onerror) {var xhr, results, url;url = 'http://example.com/search?q=' + term;xhr = new XMLHttpRequest();xhr.open('GET', url, true);xhr.onload = function (e) {if (this.status === 200) {results = JSON.parse(this.responseText);onload(results);}};xhr.onerror = function (e) {onerror(e);};xhr.send();
}search('Hello World', console.log, console.error);
如果使用 Promise 对象,就可以写成下面这样。
function search(term) {var url = 'http://example.com/search?q=' + term;var xhr = new XMLHttpRequest();var result;var p = new Promise(function (resolve, reject) {xhr.open('GET', url, true);xhr.onload = function (e) {if (this.status === 200) {result = JSON.parse(this.responseText);resolve(result);}};xhr.onerror = function (e) {reject(e);};xhr.send();});return p;
}search('Hello World').then(console.log, console.error);
加载图片的例子,也可以用 Ajax 操作完成。
function imgLoad(url) {return new Promise(function (resolve, reject) {var request = new XMLHttpRequest();request.open('GET', url);request.responseType = 'blob';request.onload = function () {if (request.status === 200) {resolve(request.response);} else {reject(new Error('图片加载失败:' + request.statusText));}};request.onerror = function () {reject(new Error('发生网络错误'));};request.send();});
}
小结
Promise 的优点在于,让回调函数变成了规范的链式写法,程序流程可以看得很清楚。它有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到它们的状态都改变以后,再执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一指定处理方法等等。
而且,Promise 还有一个传统写法没有的好处:它的状态一旦改变,无论何时查询,都能得到这个状态。这意味着,无论何时为 Promise 实例添加回调函数,该函数都能正确执行。所以,你不用担心是否错过了某个事件或信号。如果是传统写法,通过监听事件来执行回调函数,一旦错过了事件,再添加回调函数是不会执行的。
Promise 的缺点是,编写的难度比传统写法高,而且阅读代码也不是一眼可以看懂。你只会看到一堆then
,必须自己在then
的回调函数里面理清逻辑。
微任务
Promise 的回调函数属于异步任务,会在同步任务之后执行。
new Promise(function (resolve, reject) {resolve(1);
}).then(console.log);console.log(2);
// 2
// 1
上面代码会先输出2,再输出1。因为console.log(2)
是同步任务,而then
的回调函数属于异步任务,一定晚于同步任务执行。
但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。
setTimeout(function() {console.log(1);
}, 0);new Promise(function (resolve, reject) {resolve(2);
}).then(console.log);console.log(3);
// 3
// 2
// 1
上面代码的输出结果是321
。这说明then
的回调函数的执行时间,早于setTimeout(fn, 0)
。因为then
是本轮事件循环执行,setTimeout(fn, 0)
在下一轮事件循环开始时执行。
参考链接
- Sebastian Porto, Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises
- Rhys Brett-Bowen, Promises/A+ - understanding the spec through implementation
- Matt Podwysocki, Amanda Silver, Asynchronous Programming in JavaScript with “Promises”
- Marc Harter, Promise A+ Implementation
- Bryan Klimt, What’s so great about JavaScript Promises?
- Jake Archibald, JavaScript Promises There and back again
- Mikito Takada, 7. Control flow, Mixu’s Node book
Promise 对象 异步 then 回调函数相关推荐
- Promise对象的resolve回调函数和reject回调函数使用
Promise是ES6中用来结局回调地狱的问题的但是并不能帮我们减少代码量 Promise是一个构造函数 new Promise() 得到一个Promise一个实例 在Promise上有两个函数分别是 ...
- js异步解决方案 --- 回调函数 vs promise vs generater/yield vs async/await
javascript -- 深度解析异步解决方案 高级语言层出不穷, 然而唯 js 鹤立鸡群, 这要说道js的设计理念, js天生为异步而生, 正如布道者朴灵在 node深入浅出--(有兴趣的可以读一 ...
- html里的回调函数的作用域,异步与回调/函数的作用域链
异步与回调/函数的作用域链 JavaScript 只在一个线程上运行,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待. 这种模式的好处是实现起来比较简单,执行环境相对单纯:坏 ...
- 什么是promise?什么是回调函数?
promise 1.什么是promise? promise是异步编程的一种解决方案,是回调的升级版,在处理需要花费一段时间的任务时,使用promise,就可以进行异步操作,防止阻塞, 2. 这里先说一 ...
- linux异步io 回调函数,Linux异步IO
Linux中最常用的IO模型是同步IO,在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足条件为止.这是一种很好的解决方案,调用应用程序在等待IO完成的时候不需要占用CPU,但是在很多场景中 ...
- 【JavaScript】【5】定时器(包含回调函数与Promise)
文章目录 前言 一.回调函数 二. Promise promise对象 Promise对象的生成 加载图片写成一个Promise 三.定时器与清除定时器的方法 四.京东购物车倒计时案例 五.发送验证码 ...
- Promise是什么?Promise怎么使用?回调地狱
1.Promise的概念 Promise是ES6提供的原生的类(构造函数), 用来传递异步操作的消息.它代表了某个未来才会知道结果的事件(通常是一个异步操作) 2.Promise的两个特点: 1).对 ...
- javascript之异步操作理解---回调函数,async,await以及promise对象
javascript之异步操作理解---回调函数,async,await以及promise对象 概述 概述 写在前面:虽然平时做项目,但是发现自己写的代码还是很烂.最近接触了一个对性能要求比较高的项目 ...
- setwindowshookex回调函数不执行_ECMAScript 6 入门教程—Generator 函数的异步应用
作者 | 阮一峰异步编程对 JavaScript 语言太重要.JavaScript 语言的执行环境是"单线程"的,如果没有异步编程,根本没法用,非卡死不可.本章主要介绍 Gener ...
最新文章
- 一线程序员年薪90万,不敢结婚不敢要孩子,被父母怼:堂弟月薪4千二胎都有了,家里最挫的就是我!...
- SpringBoot b2b2c 多用户商城系统(八):配置中心服务化和高可用
- 2021已去,2022未来
- centos7安装最新版node
- “返回指向栈空间的指针”的错误
- php判断值和类型,php如何判断某变量的类型?
- 基于Spark的电影推荐系统(推荐系统~2)
- BLUE引擎M2运行几天后就不可以施放合击技能的方法
- win10查看计算机系统版本,如何查看我的电脑是不是win10 1909版本?
- 小白鼠测试---VR头戴设备-暴风魔镜4
- linux 自动降频,Linux资格认证:Ubuntu下对CPU进行降频
- SCRM升级--企业微信数字营销解决方案
- 智慧城市每年商机超2万亿美元;中德嘉宾共话智慧城市建设 | 智慧城市周报
- 基于魔兽RPG对当前游戏发展趋势的分析,以及尝试一个高游戏性玩法的设计
- Android Studio使用签名打包发布APP(安卓生成apk文件)
- 【哪吒社区Java技能树 打卡day2】Java学习路线总结(思维导图篇)
- 章节六:带参数请求数据
- 楼氏硅麦SPH0641LM4H-1
- 第六章 selenium自动化测试工具:天下工具为我所用
- 黑客「杀死」物联网?区块链正在成为救世主
热门文章
- 如何使用激活工具Microsoft Toolkit
- 中国县城生活实录:上楼因特网,下楼码长城
- AlexNet实现花卉识别
- python求向量与x轴的夹角_如何计算直线与水平轴的夹角?
- (附源码)ssm校园拼车服务系统 毕业设计211633
- 如何将本地项目上传到码云和github,超简单
- Learning Entity and Relation Embeddings for Knowledge Graph Completion (TransR)论文翻译
- 【Electron】酷家乐客户端开发实践分享 — 下载管理器
- wildfly安装及基本使用
- 计算机病毒在我国的发展情况,计算机病毒检测技术的现状与发展