含义

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

Promise 对象是一个代理对象(代理一个值),被代理的值在 Promise 对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的 Promise 对象。

Promise 对象有以下两个特点:

  1. 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)fulfilled(已成功)rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

基本用法

new Promise( function(resolve, reject) {...} /* executor */  );

Promise 对象的初始化接收一个执行函数 executor,executor 是带有 resolve 和 reject 两个参数的函数 。

Promise 构造函数执行时会立即调用 executor 函数, resolve 和 reject 两个函数作为参数传递给 executor(executor 函数在 Promise 构造函数返回新建对象前被调用)。

resolve 和 reject 函数被调用时,分别将 promise 的状态改为 fulfilled(完成)rejected(失败)。executor 内部通常会执行一些异步操作,一旦完成,可以调用 resolve 函数来将 promise 状态改成 fulfilled,或者在发生错误时将它的状态改为 rejected

如果在 executor 函数中抛出一个错误,那么该 promise 状态为 rejected。executor函数的返回值被忽略。

先看个示例:(注:后文的示例均使用 setTimeout 模拟异步操作

// 从 pending 变为 fulfilled
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Hi,');
resolve('promise fulfilled!');
}, 500);
}).then(function(data) {
console.log(data);
});
// Hi,
// promise fulfilled! // 从 pending 变为 rejected
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Hi,');
reject('promise rejected!');
}, 500);
}).then(null, function(error) {
console.log(error);
});
// Hi,
// promise rejected!

解释一下 从 pending 变为 fulfilled 这段代码,当执行 new Promise() 时,传入的执行函数就立即执行了,此时其内部有一个异步操作(过 500ms 之后执行),等过了 500ms 之后先执行 console.log('Hi,'); 输出 Hi,,此时 promise 的状态为 pending(进行中),而执行 resolve('Promise!'); 则修改 promise 的状态为 fulfilled(完成),然后我们调用 then() 接收 promise 在 fulfilled 状态下传递的值,此时输出 'Promise!'

同理,从 pending 变为 rejected 这段代码基本差不多,不同的是异步操作调用了 reject 方法,then 方法使用第二个参数接收 rejected 状态下传递的值。

Promise.prototype.then()

then 的作用是为 Promise 实例添加状态改变时的回调函数。

then 方法的第一个参数是 resolved 状态的回调函数,第二个参数(可选)是 rejected 状态的回调函数。

var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Hi,'); // 模拟请求,请求状态为200代表成功,不是200代表失败
if (status === 200) {
resolve('promise fulfilled!');
} else {
reject('promise rejected!');
}
}, 500);
}).then(function(data) {
console.log(data);
}, function(error) {
console.log(error);
});
// 如果调用 resolve 方法,输出如下:
// Hi,
// promise fulfilled! // 如果调用 reject 方法,输出如下:
// Hi,
// promise rejected!

then 方法返回的是一个新的 Promise 实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。采用链式的 then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个 Promise 对象(即有异步操作),这时后一个回调函数,就会等待该 Promise 对象的状态发生变化,才会被调用。

var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Hi,');
resolve();
}, 500);
}).then(function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
// 模拟请求,请求状态为200代表成功,不是200代表失败
if (status === 200) {
resolve('promise fulfilled!');
} else {
reject('promise rejected!');
}
});
})
}).then(function(data) {
console.log(data);
}, function(error) {
console.log(error);
});
// 如果第一个 then 调用 resolve 方法,第二个 then 调用第一个回调函数,最终输出如下:
// Hi,
// promise fulfilled! // 如果第一个 then 调用 reject 方法,第二个 then 调用第一个回调函数,最终输出如下:
// Hi,
// promise rejected!

Promise.prototype.catch()

catch 方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

所以下面代码:

var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Hi,'); // 模拟请求,请求状态为200代表成功,不是200代表失败
if (status === 200) {
resolve('promise fulfilled!');
} else {
reject('promise rejected!');
}
}, 500);
}).then(function(data) {
console.log(data);
}, function(error) {
console.log(error);
});

等价于:

var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Hi,'); // 模拟请求,请求状态为200代表成功,不是200代表失败
if (status === 200) {
resolve('promise fulfilled!');
} else {
reject('promise rejected!');
}
}, 500);
}).then(function(data) {
console.log(data);
}).catch(function(error) {
console.log(error);
});

如果没有使用 catch 方法或者 then 第二个参数指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应,这跟传统的 try/catch 代码块是不同。

catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then 方法。

catch 方法与 .then(null, rejection) 的不同:

  • 如果异步操作抛出错误,状态就会变为 rejected,就会调用 catch 方法指定的回调函数,处理这个错误。
  • then 方法指定的回调函数,如果运行中抛出错误,也会被 catch 方法捕获。
  • catch 方法的写法更接近同步的写法(try/catch)。

因此,建议总是使用 catch 方法,而不使用 then 方法的第二个参数。

Promise.prototype.finally()

finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

var p = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('Hi,'); // 模拟请求,请求状态为200代表成功,不是200代表失败
if (status === 200) {
resolve('promise fulfilled!');
} else {
reject('promise rejected!');
}
}, 500);
}).then(function(data) {
console.log(data);
}).catch(function(error) {
console.log(error);
}).finally(function() {
console.log('I am finally!');
});

上面代码中,不管 promise 最后的状态,在执行完 then 或 catch 指定的回调函数以后,都会执行 finally 方法指定的回调函数。

Promise.all()

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

var p = Promise.all([p1, p2]);

上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1、p2决定,分成两种情况。

(1)只有 p1、p2 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1、p2 的返回值组成一个数组,传递给 p 的回调函数。

(2)只要 p1、p2 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。

示例:

试想一个页面聊天系统,我们需要从两个不同的 URL 分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现。

// 并行执行异步任务
var p1 = new Promise(function (resolve, reject) {
setTimeout(function() {
// 模拟请求,请求状态为200代表成功,不是200代表失败
if (status === 200) {
resolve('P1');
} else {
reject('error');
}
}, 500);
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 输出:['P1', 'P2']
}).catch(function(error) {
console.log(error); // 如果p1执行失败,则输出:error
});

注意,如果作为参数的 Promise 实例,自己定义了 catch 方法,那么它一旦被 rejected,并不会触发 Promise.all() 的 catch 方法。

Promise.race()

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

var p = Promise.race([p1, p2]);

上面代码中,只要 p1、p2 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

Promise.race 方法的参数与 Promise.all 方法一样,如果不是 Promise 实例,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。

示例:

有些时候,多个异步任务是为了容错。比如,同时向两个 URL 读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现。

// 多任务容错
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 400, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P2'
});

Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve 方法就起到这个作用。

Promise.resolve方法的参数分成四种情况:

(1)参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

(2)参数是一个 thenable 对象

thenable 对象指的是具有 then 方法的对象,比如下面这个对象。

var thenable = {
then: function (resolve, reject) {
resolve(42);
}
};

Promise.resolve 方法会将这个对象转为 Promise 对象,然后就立即执行 thenable 对象的 then 方法。

var thenable = {
then: function (resolve, reject) {
resolve(42);
}
}; var p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});

上面代码中,thenable 对象的 then 方法执行后,对象 p1 的状态就变为 resolved,从而立即执行最后那个 then 方法指定的回调函数,输出 42。

(3)参数不是具有 then 方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved。

var p = Promise.resolve('Hello');

p.then(function (s) {
console.log(s)
});
// 'Hello' var p1 = Promise.resolve(true); p1.then(function (b) {
console.log(b)
});
// true var p2 = Promise.resolve(1); p1.then(function (n) {
console.log(n)
});
// 1

(4)不带有任何参数

Promise.resolve 方法允许调用时不带参数,直接返回一个 resolved 状态的 Promise 对象。

所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用 Promise.resolve 方法。

Promise.reject()

Promise.reject 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。

注意,Promise.reject 方法的参数,会原封不动地作为 reject 的参数,变成后续方法的参数。这一点与 Promise.resolve 方法不一致。

var thenable = {
then(resolve, reject) {
reject('出错了');
}
}; Promise.reject(thenable)
.catch(e = > {
console.log(e === thenable)
})
// true

上面代码中,Promise.reject 方法的参数是一个 thenable 对象,执行以后,后面 catch 方法的参数不是 reject 抛出的 出错了 这个字符串,而是 thenable 对象。

应用

加载图片

我们可以将图片的加载写成一个 Promise,一旦加载完成,Promise 的状态就发生变化。

function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};

封装ajax

我们可以将 ajax 请求写成一个 Promise,根据请求的不同状态改变 Promise 的状态。

function ajax(method, url, data) {
var request = new XMLHttpRequest();
return new Promise(function (resolve, reject) {
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
resolve(request.responseText);
} else {
reject(request.status);
}
}
};
request.open(method, url);
request.send(data);
});
}

总结

优点:

  • 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(回调地狱)。
  • 在异步执行的流程中,可以把执行代码和处理结果的代码清晰地分离开来。

缺点:

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

参考资料

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

http://es6.ruanyifeng.com/#docs/promise

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345008539155e93fc16046d4bb7854943814c4f9dc2000

JavaScript之Promise对象的更多相关文章

  1. JavaScript 初识Promise 对象

    什么是Promise? 其实, Promise就是一个类,而且这个类已经成为ES6的标准,是 ECMAScript 6 规范的重要特性之一.这个类目前在chrome32.Opera19.Firefox ...

  2. 浅谈Javascript中Promise对象的实现

    https://segmentfault.com/a/1190000000684654 What? Promise是CommonJS的规范之一,拥有resolve.reject.done.fail.t ...

  3. JavaScript ES6 Promise对象

    说明 Node.js中,以异步(Async)回调著称,使用了异步,提高了程序的执行效率,但是,代码可读性较差的. 假如有几个异步操作,后一个操作需要前一个操作的执行完毕之后返回的数据才能执行下去,如果 ...

  4. Javascript中Promise对象的实现

    http://segmentfault.com/a/1190000000684654 http://www.infoq.com/cn/news/2011/09/js-promise/

  5. JavaScript异步编程(1)- ECMAScript 6的Promise对象

    JavaScript的Callback机制深入人心.而ECMAScript的世界同样充斥的各种异步操作(异步IO.setTimeout等).异步和Callback的搭载很容易就衍生"回调金字 ...

  6. 通过一道笔试题浅谈javascript中的promise对象

    因为前几天做了一个promise对象捕获错误的面试题目,所以这几天又重温了一下promise对象.现在借这道题来分享下一些很基础的知识点. 下面是一个面试题目,三个promise对象捕获错误的例子,返 ...

  7. JavaScript:学习笔记(9)——Promise对象

    JavaScript:学习笔记(9)——Promise对象 引入Promise Primose是异步编程的一种解决方案,比传统的解决方案回调函数和事件更加合理和强大.如下面为基于回调函数的Ajax操作 ...

  8. Javascript Promise对象学习

    ES6中的Promise对象 var p = new Promise(function(resolve, reject){ window.setTimeout(function(){ console. ...

  9. JavaScript - Promise对象

    优点: 在异步执行的流程中,把执行代码和处理结果的代码清晰地分离(因为Promise对象有链式写法,有then和catch) 组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行 ...

随机推荐

  1. hackme.inndy.tw - pyyy - Writeup

    hackme.inndy.tw - pyyy - Writeup 0x01 反编译 1.第一次尝试的时候我直接在线反编译,部分结果如下. for (i, f) in enumerate(F): n = ...

  2. C++之异常捕获和处理

    一.简介   在C++语言中,异常处理包括:throw表达式,try语句块,一套异常类.其中,异常类用于在throw表达式和相关的catch子句之间传递异常的具体信息.exception头文件定义了最 ...

  3. Alpha冲刺Day9

    Alpha冲刺Day9 一:站立式会议 今日安排: 经过为期5天的冲刺,基本完成企业人员模块的开发.因第三方机构与企业存在委托的关系.第三方人员对于风险的自查.风险列表的展示以及自查风险的统计展示(包 ...

  4. io多路复用(三)

    #!/usr/bin/env python # -*- coding:utf-8 -*- import socket sk1 = socket.socket() sk1.bind(('127.0.0. ...

  5. Beta冲刺Day3

    项目进展 李明皇 今天解决的进度 完善了程序的运行逻辑(消息提示框等) 明天安排 前后端联动调试 林翔 今天解决的进度 向微信官方申请登录验证session以维护登录态 明天安排 继续完成维护登录态 ...

  6. Linux 下的权限改变与目录配置

    Linux 下的权限改变与目录配置 ./代表本目录的意思. (1):用户与用户组, 1:文件所有者,文件被某一用户所有 2:用户组:    对文件给与一个或者多个用户权限配置 3:其它人: (2):l ...

  7. OpenCASCADE Trihedron Law

    OpenCASCADE Trihedron Law eryar@163.com Abstract. In differential geometry the Frenet-Serret formula ...

  8. Linux的安装和使用技巧

    LinuxCentOs开始设置一个普通的用户,如果想进入root用户,可以su然后设置密码,然后第二次再次输入su,然后输入相同的密码就可以进去了 有很多命令需要在root下才能执行,但是在创建时却是 ...

  9. java中final 关键字的作用

    final 关键字的作用 java中的final关键字可以用来声明成员变量.本地变量.类.方法,并且经常和static一起使用声明常量. final关键字的含义: final在Java中是一个保留的关 ...

  10. Mego开发文档 - 数据库建模

    数据库建模 我们还提供了一些其他的特性,用于定制化数据库对应的数据结构. 表映射 框架默认会使用CLR类型名称做为实际数据库的表名,当两者不一致时可以使用该特性强制表名称. [Table(" ...