共计 6244 个字符,预计需要花费 16 分钟才能阅读完成。
因为 JavaScript 通常是由单线程来执行代码,所以在编写 JavaScript 代码时常常须要应用异步操作来进步程序性能。一般来说异步执行在 JavaScript 中应用 回调函数
的模式来实现。不过近年来因为社区的推动,Promise
曾经成为 JavaScript 异步编程的一个规范,应用 Promise
进行异步编程,代码的可维护性将有很大晋升,尤其是应用 Promise
取代多层 回调函数
嵌套的问题。
Promise
简介
上面是结构一个最简略的 Promise
代码示例:
// 结构 Promise
const promise = new Promise(function (resolve, reject) {
// 异步操作代码
if (异步操作胜利) {resolve(this.response);
} else {reject(this.status);
}
});
// promise 状态扭转时的回调
promise.then(function (res) {console.log(res); // 执行胜利回调
}, function (error) {console.log(error); // 执行失败回调
})
Promise
是 JavaScript 提供的一个对象,能够通过 new
关键字结构这个对象。Promise
构造函数接管一个函数作为参数,该函数接管另外两个函数作为参数,别离是 resolve
、reject
,这两个函数由 JavaScript 引擎本身提供,无需咱们手动创立。
Promise
对象有三种状态,别离是:pending
(进行中)、fulfilled
(已胜利)、rejected
(已失败)。有两种状态扭转,一种是从 pending
变为 fulfilled
,另一种是从 pending
变为 rejected
,一旦状态扭转,状态就会凝固,无奈被再次更改。而 resolve
的作用就是将 Promise
对象的状态从 pending
变为 fulfilled
,reject
的作用是将 Promise
对象的状态从 pending
变为 rejected
。
通常咱们在传递给 Promise
构造函数的函数顶部编写一些异步代码,例如一个 AJAX
申请,而后当异步代码执行胜利的时候,调用 resolve
,resolve
是一个函数,它可能接管参数,所以咱们调用它时能够将异步代码的执行后果传递给它,此时 resolve
函数有两个作用,一是扭转 Promise
的状态为 fulfilled
,二是它可能将传递进去的参数传递到 promise
实例的 then
办法的第一个回调函数中,待后续操作应用。异步代码执行失败时调用 reject
函数,此时 reject
函数同样有两个作用,一是扭转 Promise
的状态为 rejected
,二是将传递进去的参数传递到 promise
实例的 then
办法的第二个回调函数中。
当 Promise
状态一旦扭转,咱们就能够通过 Promise
对象的实例(promise
变量)的 then
办法获取到异步操作的后果。then
办法接管两个回调函数作为参数,第一个回调函数会在 Promise
状态变为 fulfilled
的时候主动调用,第二个回调函数会在 Promise
状态变为 rejected
的时候主动调用。这两个回调函数别离接管一个参数,第一个回调函数接管到的 res
参数实际上就是咱们在 Promise
外部异步代码执行胜利时传递给 resolve
的参数,第二个回调函数接管到的 error
参数是在 Promise
外部异步代码执行失败时传递给 reject
的参数。
以上就是 Promise
对象的执行流程,接下来咱们就用一个示例来展现 Promise
在理论编写代码中如何利用。
先来看一段用 回调函数
的写法发送 AJAX
申请的代码示例:
function handleResponse() {if (this.readyState === 4) {if (this.status === 200) {console.log(this.response);
} else {console.log(this.status);
}
}
}
const request = new XMLHttpRequest();
request.open('GET', 'http://httpbin.org/get');
request.onreadystatechange = handleResponse;
request.send();
在编写 Javascript 时这种代码十分常见,那么如何用 Promise
的写法来编写下面这段代码呢?请看上面的示例:
const promise = new Promise(function (resolve, reject) {function handleResponse() {if (this.readyState === 4) {if (this.status === 200) {resolve(this.response); // 申请胜利时调用
} else {reject(this.status); // 申请失败时调用
}
}
}
const request = new XMLHttpRequest();
request.open('GET', 'http://httpbin.org/get');
request.onreadystatechange = handleResponse;
request.send();})
promise.then(function (res) {console.log(res); // 胜利回调
}, function (error) {console.log(error); // 失败回调
})
以上就是通过 Promise
来解决 AJAX
的写法。乍一看,感觉代码是不是变多了,也更麻烦了?事实上,的确如此,如果仅仅从发送一个简略的 AJAX
申请代码来看咱们的确没必要采纳 Promise
的写法,这样做只会让代码看起来更加简单。
Promise
可能解决什么问题
那么 Promise
的实用场景到底是什么,可能解决什么问题呢?
Promise
非常适合解决回调天堂
问题。Promise
解决了回调函数中不可能应用return
的问题。
咱们来看看 Promise
是如何解决这两个问题的。假如咱们须要调用一个获取用户发表的博客列表的接口,而这个接口须要登录才可能调用。也就是说咱们调用接口的程序必须是:先调用登录认证接口进行登录,而后再去调用获取用户博客列表的接口。
如果应用 jQuery
发送 AJAX
申请的 回调函数
写法,咱们有可能会写出这样的代码:
// 发送第一个 AJAX 申请进行登录
$.ajax({
type: 'GET',
url: 'http://localhost:3000/login',
success: function (res) {
// 登录胜利回调函数外部,发送第二个 AJAX 申请获取数据
$.ajax({
type: 'GET',
url: 'http://localhost:3000/blog/list',
success: function (res) {console.log(res);
},
error: function (xhr) {console.log(xhr.status);
}
});
},
error: function (xhr) {console.log(xhr.status);
}
});
能够看到,应用 回调函数
模式写出的代码是嵌套模式的。也就是说,每多一次申请,咱们就要在 success
回调函数外部再写一层嵌套。如果嵌套档次过多,那么就会呈现 JavaScript 令人头疼的问题 —— 回调天堂
。嵌套档次越多,代码就越难以了解,如果出现异常,那么调试将会是一个十分苦楚的过程。
咱们尝试着用 Promise
来解决下面的问题:
const getResponse = function (url) {const promise = new Promise(function (resolve, reject) {
$.ajax({
type: 'GET',
url: url,
success: function (res) {resolve(res);
},
error: function (xhr) {reject(xhr);
}
});
});
return promise;
}
getResponse('http://localhost:3000/login').then(function (res) {getResponse('http://localhost:3000/blog/list').then(function (res) {console.log(res);
}, function (xhr) {console.log(xhr.status);
})
}, function (xhr) {console.log(xhr.status);
});
以上是应用 Promise
进行重构的代码,但仔细观察你会发现:下面的代码仍然在用 回调
的思维来写 Promise
代码。在第一次调用 getResponse
函数申请 http://localhost:3000/login
胜利后,代码执行 then
办法,在 then
办法的外部,再一次调用了 getResponse
函数,这一次申请 http://localhost:3000/blog/list
地址,胜利后执行下一个 then
办法,在 then
办法外部就可能胜利获取用户博客列表的接口返回后果。
其实下面这段代码同样存在 回调天堂
的问题,因为如果申请次数过多,咱们同样须要在一层层的嵌套函数中调用 getResponse
。这无疑没有解脱 回调天堂
。
来看正确应用 Promise
对象解决 回调天堂
的示例:
const getResponse = function (url) {const promise = new Promise(function (resolve, reject) {
$.ajax({
type: 'GET',
url: url,
success: function (res) {resolve(res);
},
error: function (xhr) {reject(xhr.status);
}
});
});
return promise;
}
getResponse('http://localhost:3000/login').then(function (res) {return getResponse('http://localhost:3000/blog/list');
}, function (xhr) {console.log(xhr.status);
}).then(function (res) {console.log(res);
}, function (xhr) {console.log(xhr.status);
});
在 Promise
接口中,then
办法会返回一个新的 Promise
实例,所以咱们可能采纳链式调用的写法,then
办法前面再接另一个 then
办法,实践上能够有限的接下去。
留神第一个 then
办法的外部,在调用 getResponse('http://localhost:3000/blog/list')
的后面加上了 return
。正是这个 return
的存在,才使得不应用层层嵌套的写法成为可能,得以使代码展平。Promise
实例前面的 then
办法会顺次执行,前一次 then
办法外部的 return
后果,会当作参数传入下一个 then
办法。如果应用回调函数的写法,因为其外部没有方法 return
,所以你只能有限的减少嵌套来解决问题。
这段代码能力体现出 Promise
对象的真正威力,它可能把层层嵌套的代码全副展平。每多一次申请,就在前面再减少一个 then
办法的调用,这样写进去的代码显著要直观很多,你不须要再写出多层嵌套的代码。
Promise
的 then
办法和 catch
办法
通过后面的介绍咱们晓得 Promise
的 then
办法接管两个回调函数作为参数。实际上,它的第二个参数并不是必填的,如果你不关怀异样,那么就能够只写第一个回调函数。
Promise
还提供了 catch
办法专门用来接管异样,写法如下:
getResponse('http://localhost:3000/login').then(function (res) {return getResponse('http://localhost:3000/blog/list');
}).catch(function (xhr) {console.log(xhr.status);
});
用下面这种写法来解决异样更合乎直觉,看起来也更加清晰,不必在 then
办法外部传入第二个回调函数去解决异样,而是独自在 catch
办法外部进行解决。事实上 .catch(callback)
办法等价于 .then(undefined, callback)
。
Promise
的 all
办法和 race
办法
Promise
的 all
办法能够将多个 Promise
实例,合并为一个 Promise
实例。
这有什么用?假如咱们须要期待多个异步操作都执行完能力执行下一步逻辑,此时就是应用 all
办法的绝佳机会。这种状况应用 回调函数
的模式不太好解决,而应用 Promise
就非常简单。
咱们通过申请三次 http://httpbin.org/get
来模仿须要期待的三个异步操作,写出的代码如下:
const p1 = getResponse('http://httpbin.org/get');
const p2 = getResponse('http://httpbin.org/get');
const p3 = getResponse('http://httpbin.org/get');
const p = Promise.all([p1, p2, p3]);
p.then(res => {console.log(res);
}).catch(err => {console.log(err);
});
all
办法接管一个数组作为参数,数组每一项都是一个 Promise
对象,Promise
实例 p
就是合并后的对象,p1
、p2
、p3
的状态决定了 p
的最终状态。
只有 p1
、p2
、p3
的状态同时都变为 fulfilled
时,p
的状态才会变为 fulfilled
。只有 p1
、p2
、p3
的状态有一个变为 rejected
,p
的状态就会变为 rejected
。当 p
的状态变为 fulfilled
时,p1
、p2
、p3
的返回后果会组成一个列表,主动传递给 p
的 then
办法的回调函数。并且这个列表的程序由传入 all
办法的数组中 p1
、p2
、p3
的程序决定。
当 p1
、p2
、p3
任意一个状态变为 rejected
时,只有其没有本人的 catch
办法时才会调用 all
办法前面的 catch
办法。否则只会调用其本人的 catch
办法。
至于 race
办法的用法与 all
办法完全相同,其区别是:只有 p1
、p2
、p3
的状态有一个变为 fulfilled
时,p
的状态就会变为 fulfilled
。这也是 race
这个单词所代表的含意 竞争
,只有 p1
、p2
、p3
其中有一个对象率先扭转了状态,那么 p
的状态也就随之扭转并且凝固。其余两个对象外部的代码还是会执行,但其后果曾经没有用了。
因为应用 race
的代码只须要将 Promise.all([p1, p2, p3])
换成 Promise.race([p1, p2, p3])
即可,所以这里不再给出代码示例。
以上就是对于 Promise
应用办法的介绍,事实上 Promise
不止提供了这些接口,不过其余接口很少会用到,所以这里不再过多介绍,等你相熟了它的用法后能够再进一步去学习。
首发地址:https://jianghushinian.cn/