Promise是一种异步编程的解决方案,比起传统的回调函数方式,Promise要更合理和强大。
下面直接给出代码,通过代码对Promise对象进行讲解和分析:
构造一个Promise实例的模版代码:
var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } }); promise.then(function(value) { // success }, function(error) { // failure });首先,Promise在ES6中是一个构造函数,用来生成Promise实例。所以,当我们需要Promise对象的时候,直接用new Promise(…)这样的方式即可。
我们调用new Promise方法的时候,它接受一个函数作为参数,而这个函数的两个参数分别是resolve, reject(我们现在并不用关心这个函数以及它的参数是从何而来的,我们首先只要按照这样的格式书写代码并会用就可以了),resolve和reject这两个参数其实也都是系统提供的函数,我们可以在这个函数中(也就是如上代码中// … some code的后面)的适当时机(比如:异步调用成功/失败后)可以直接调用它们。
调用后,Promise对象就会自动的触发then方法,如果我们调用了resolve方法,表示异步调用成功,那么就会执行作为then方法第一个参数的函数;如果我们调用了reject方法,表示异步调用失败,那么就会执行作为then方法第二个参数的函数。
补充一点:Promise对象有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。调用resolve方法可以使Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),而reject则将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected)。这两个函数都可以传递参数value或error,将可以将异步操作的结果传递出去,我们可以在then方法的参数(其实也是一个函数)中取到这个value或error。
另外,Promise新建后就会立即执行。
再看一个具体的例子,图片的异步获取:
function loadImageAsync(url) { return new Promise(function(resolve, reject) { var image = new Image(); image.onload = function() { resolve(image); }; image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; image.src = url; }); }这个栗子只写了第一段代码模版的第一部分,在这段代码中,我们就能清楚的看到何时以及如何调用resolve方法和reject方法:在图片加载成功的回调函数中调用了resolve,在图片加载失败的回调函数中调用了reject。
在前文中我们已经知道了:如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。
reject函数的参数通常是Error对象的实例,表示抛出的错误,这样,我们在回调函数中就可以接受并处理这个错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作的结果有可能是另一个异步操作。
例如:
var p1 = new Promise(function (resolve, reject) { // ... }); var p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })此时,p1的状态就会决定p2的状态——如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是Resolved或者Rejected,那么p2的回调函数将会立刻执行。
栗子:
var p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) var p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2 .then(result => console.log(result)) .catch(error => console.log(error)) // Error: fail上面代码中,Promise实例p1在3秒之后变为rejected。而p2的状态在1秒之后改变,但是resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了2秒,p1变为rejected,才能导致触发catch方法指定的回调函数。
then方法定义在Promise.prototype上,也就是说,是Promise实例的方法。因此,如果then方法中返回一个新的Promise实例,那么,then方法就可以被链式的调用,事实上,也正是如此;但是需要注意,then方法中返回的Promise已经不是调用then的那个Promise对象了,它是一个全新的promise对象。
例如像这样: 注:then方法中,第二个参数(对错误的处理函数)可以省略。
getJSON("/posts.json").then(function(json) { return json.message; }).then(function(message) { // ... });由于getJSON返回的是一个Promise对象,那么它后面的then函数就会等待这个Promise对象的状态发生变化后被执行。而在这个then函数中,返回的json.message并不是一个异步操作,那么,它下面的一个then(第二个then)就会直接被调用。而如果,在这个then函数中返回的是一个promise对象,那么第二个then函数就会等到这个promise完成后(状态变为resolve或者reject)执行。
在下面这个栗子中,几个then函数会接连被调用,依次显示hello, world, !。
function printHello (ready) { return new Promise(function (resolve, reject) { if (ready) { resolve("Hello"); } else { reject("Good bye!"); } }); } function printWorld () { alert("World"); } function printExclamation () { alert("!"); } printHello(true) .then(function(message){ alert(message); }) .then(printWorld) .then(printExclamation);Promise.prototype.catch方法是.then(null, rejection)的别名,(也就是then方法调用时省略第一个参数),它用于指定发生错误时的回调函数。
p1.then(function(posts) { // ... }).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error); });在上面这个栗子中,假设p1是一个已经定义过的promise对象,当p1中异步操作执行完毕,如果p1状态变为resolve,就会触发第一个then方法;如果p1状态变为reject,则会触发后面的catch方法(因为promise的错误具有冒泡的性质,会一直向后传递,直到被捕获)。同时,如果p1的then方法出现了错误,也会被catch函数捕获。
同时,由于catch函数实际上是then(null,rejection)的别名,所以在catch后,还可以继续链式调用then和catch函数。
由于Promise的错误具有冒泡的性质,于是建议大家总是使用catch,而不要在then方法中定义reject时的回调函数:
// 不推荐这种写法,这样在then的第一个函数参数中出现的错误无法被捕获 promise1 .then(function(data) { // success }, function(err) { // error }); // 推荐的方法,这种情况下,错误一定能被捕获 promise2 .then(function(data) { // success }) .catch(function(err) { // error });另外,在非chrome浏览器中,promise对象中出现的错误不会被传递到外层代码。