試想一下,有 3 個異步請求,第二個須要依賴第一個請求的返回結果,第三個須要依賴第二個請求的返回結果,通常怎麼作?javascript
try{
// 請求1
$.ajax({
url: url1,
success: function(data1){
// 請求2
try{
$.ajax({
url: url1,
data: data1,
success: function(data2){
try{
// 請求3
$.ajax({
url: url1,
data: data2,
success: function(data3){
// 後續業務邏輯...
}
});
}catch(ex3){
// 請求3的異常處理
}
}
})
}catch(ex){
// 請求2的異常處理
}
}
})
}catch(ex1){
// 請求1的異常處理
}
複製代碼
顯然,若是再加上覆雜的業務邏輯、異常處理,代碼會更臃腫。在一個團隊中,對這種代碼的 review 和維護將會很痛苦。前端
回調地獄帶來的負面做用有如下幾點:java
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
promise1.then(function(value) {
console.log(value);
// after 300ms, expected output: "foo"
});
複製代碼
一個 Promise 有 3 種狀態:git
pending 狀態的 Promise 可能會變爲fulfilled 狀態,也可能變爲 rejected 狀態。github
Promise 對象的狀態,只有內部可以改變(並且只能改變一次),不受外界影響。面試
對象的狀態一旦改變,就不會再變,任什麼時候候均可以獲得這個結果。 Promise 對象的狀態改變,只有兩種可能:從 Pending 變爲 Resolved 和從 Pending 變爲 Rejected。一旦狀態發生改變,狀態就凝固了,會一直保持這個結果。ajax
const p = new Promise((resolve, reject)=>{
resolve("resolved first time!"); // 只有第一次有效
resolve("resolved second time!");
reject("rejected!");
});
p.then(
(data)=>console.log(data),
(error)=>console.log(error)
);
複製代碼
// 1. 構造方法
const p = new Promise((resolve, reject) => { /* executor*/
// 1.1. Promise構造函數執行時當即調用 executor 函數;
// 1.2. resolve 和 reject 函數被調用時,分別將promise的狀態改成fulfilled(完成)或rejected(失敗)
// 1.3. 若是在executor函數中拋出一個錯誤,那麼該promise 狀態爲rejected。
// 1.4. executor函數的返回值被忽略。
});
// 2.原型方法
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
// 3.靜態方法
Promise.all(iterable);
Promise.race(iterable);
Promise.reject(reason);
Promise.resolve(value);
複製代碼
function imgLoad(url) {
// Create new promise with the Promise() constructor;
// This has as its argument a function
// with two parameters, resolve and reject
return new Promise(function(resolve, reject) {
// Standard XHR to load an image
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'blob';
// When the request loads, check whether it was successful
request.onload = function() {
if (request.status === 200) {
// If successful, resolve the promise by passing back the request response
resolve(request.response);
} else {
// If it fails, reject the promise with a error message
reject(Error('Image didn\'t load successfully; error code:' + request.statusText));
}
};
request.onerror = function() {
// Also deal with the case when the entire request fails to begin with
// This is probably a network error, so reject the promise with an appropriate message
reject(Error('There was a network error.'));
};
// Send the request
request.send();
});
}
// Get a reference to the body element, and create a new image object
var body = document.querySelector('body');
var myImage = new Image();
// Call the function with the URL we want to load, but then chain the
// promise then() method on to the end of it. This contains two callbacks
imgLoad('myLittleVader.jpg').then(function(response) {
// The first runs when the promise resolves, with the request.response
// specified within the resolve() method.
var imageURL = window.URL.createObjectURL(response);
myImage.src = imageURL;
body.appendChild(myImage);
// The second runs when the promise
// is rejected, and logs the Error specified with the reject() method.
}, function(Error) {
console.log(Error);
});
複製代碼
Event Loop 中的事件,分爲 MacroTask(宏任務)和 MicroTask(微任務)。npm
通俗來講,MacroTasks 和 MicroTasks 最大的區別在它們會被放置在不一樣的任務調度隊列中。 編程
注:async/await 本質上仍是基於Promise的一些封裝,而Promise是屬於微任務的一種。因此在使用 await 關鍵字與 Promise.then 效果相似。即:async 函數在 await 以前的代碼都是同步執行的,能夠理解爲await以前的代碼屬於new Promise時傳入的代碼,await以後的全部代碼都是在Promise.then中的回調;數組
setTimeout(function(){
console.log(1);
}, 0)
new Promise(function(resolve){
console.log(2);
resolve();
console.log(3);
}).then(function(){
console.log(4);
})
console.log(5);
複製代碼
答案 & 解析:
// 解析:
// 1. new Promise(fn)後,函數fn會當即執行;
// 2. fn在執行過程當中,因爲調用了resolve,使得Promise當即轉換爲resolve狀態,
// 這也促使p.then(fn)中的函數fn被當即放入microTask隊列中,所以fn將會在
// 本輪事件循環的結束時執行,而不是下一輪事件循環的開始時執行;
// 3. setTimeout屬於macroTask,是在下一輪事件循環中執行;
//答案:
// 2 3 5 4 1
複製代碼
Promise.resolve(1)
.then((res) => {
console.log(res);
return 2;
})
.catch((res) => {
console.log(res);
return 3;
})
.then((res) => {
console.log(res);
});
複製代碼
答案 & 解析:
// 解析:每次調用p.then或p.catch都會返回一個新的promise,
// 從而實現了鏈式調用;第一個.then中未拋出異常,
// 因此不會被.catch語句捕獲,會正常進入第二個.then執行;
// 答案:1 2
複製代碼
Promise.resolve()
.then( () => {
return new Error('error!')
})
.then( res => {
console.log('then: ', res)
})
.catch( err => {
console.log('catch: ', err)
});
複製代碼
答案 & 解析:
// 解析:在 .then 或 .catch 中 return 一個 error 對象並不會拋出錯誤,
// 因此不會被後續的 .catch 捕獲;
// 答案:then: Error: error!
// at ...
// at ...
複製代碼
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log);
複製代碼
答案 & 解析:
// 解析:p.then、.catch 的入參應該是函數,傳入非函數則會發生值穿透;
// 答案:1
複製代碼
Promise.resolve()
.then(
value => { throw new Error('error'); },
reason => { console.error('fail1:', reason); }
)
.catch(
reason => { console.error('fail2:', reason); }
);
複製代碼
答案 & 解析:
// 解析:.then能夠接收兩個參數:.then(onResolved, onRejected)
// .catch是.then的語法糖:.then(onRejected) ==> .then(null, onRejected)
// 答案:fail2: Error: error
// at .....
// at .....
複製代碼
console.log(1);
new Promise(function (resolve, reject){
reject();
resolve();
}).then(function(){
console.log(2);
}, function(){
console.log(3);
});
console.log(4);
複製代碼
答案 & 解析:
// 解析:Promise狀態的一旦變成resolved或rejected,
// Promise的狀態和值就固定下來了,
// 不論你後續再怎麼調用resolve或reject方法,
// 都不能改變它的狀態和值。
//
// 答案:1 4 3
複製代碼
new Promise(resolve => { // p1
resolve(1);
// p2
Promise.resolve().then(() => {
console.log(2); // t1
});
console.log(4)
}).then(t => {
console.log(t); // t2
});
console.log(3);
複製代碼
答案 & 解析:
// 解析:
// 1. new Promise(fn), fn 當即執行,因此先輸出 4;
// 2. p1和p2的Promise在執行then以前都已處於resolve狀態,
// 故按照then執行的前後順序,將t一、t2放入microTask中等待執行;
// 3. 完成執行console.log(3)後,macroTask執行結束,而後microTask
// 中的任務t一、t2依次執行,因此輸出三、二、1;
// 答案:
// 4 3 2 1
複製代碼
Promise.reject('a')
.then(()=>{
console.log('a passed');
})
.catch(()=>{
console.log('a failed');
});
Promise
.reject('b')
.catch(()=>{
console.log('b failed');
})
.then(()=>{
console.log('b passed');
})
複製代碼
答案 & 解析:
// 解析:p.then(fn)、p.catch(fn)中的fn都是異步執行,上述代碼可理解爲:
// setTimeout(function(){
// setTimeout(function(){
// console.log('a failed');
// });
// });
// setTimeout(function(){
// console.log('b failed');
//
// setTimeout(function(){
// console.log('b passed');
// });
// });
// 答案:b failed
// a failed
// b passed
複製代碼
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start');
setTimeout(function () {
console.log('settimeout')
})
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
})
console.log('script end');
複製代碼
答案:(不解析了,你們研究一下)
script start
async1 start
async2
promise1
script end
promise2
async1 end
settimeout
複製代碼
Promise有不少社區規範,如 Promise/A、Promise/B、Promise/D 以及 Promise/A 的升級版 Promise/A+;Promise/A+ 是 ES6 Promises 的前身,並且網絡上有不少可供學習、參考的開源實現(例如:Adehun、bluebird、Q、ypromise等)。
Promise/A+ 規範:
github.com/promises-ap…
用官方的Promise規範測試集,測試本身的實現。
Promise/A+ 規範測試集:
github.com/promises-ap…
能夠看出,共需實現7個接口;
複製代碼
能夠看出,7個接口中,只有構造函數RookiePromise和成員函數then算核心接口,其餘接口都可經過這兩個接口實現;
複製代碼
Promise 對象的狀態改變,只有兩種可能:pending -> fulfilled 和 pending -> rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果;
——《ES6 標準入門(第三版)》
/** * 2.1. Promise States * A promise must be in one of three states: * pending, fulfilled, or rejected. */
const STATE_PENDING = "pending";
const STATE_FULFILLED = "fulfilled";
const STATE_REJECTED = "rejected";
function RookiePromise(fn) {
this._state = STATE_PENDING;
this._value = undefined;
this._callbacks = [];
this._errorbacks = [];
/** * 2.3. The Promise Resolution Procedure * The promise resolution procedure is an abstract operation * taking as input a promise and a value, which we denote as * [[Resolve]](promise, x) */
var executed = false; // 用於保證resolve接口只有第一次被觸發時有效;
function resolve(promise, x){
if(executed){
return;
}
executed = true;
var innerResolve = (promise, x) => {
if(promise === x){
// 2.3.1. If promise and x refer to the same object,
// reject promise with a TypeError as the reason.
this._reject(new TypeError("出錯了, promise === x, 會形成死循環!"));
}else if(x instanceof RookiePromise){
// 2.3.2. If x is a promise, adopt its state [3.4]:
// 2.3.2.1. If x is pending, promise must remain pending until x is fulfilled or rejected.
// 2.3.2.2. If/when x is fulfilled, fulfill promise with the same value.
// 2.3.2.3. If/when x is rejected, reject promise with the same reason.
if(x._state == STATE_PENDING){
x.then((value) => {
innerResolve(promise, value);
}, (reason) => {
this._reject(reason);
});
}else if(x._state == STATE_FULFILLED){
this._fulfill(x._value);
}else if(x._state == STATE_REJECTED){
this._reject(x._value);
}
}else if(x && (typeof x == "function" || typeof x == "object")){
// 2.3.3. Otherwise, if x is an object or function,
try{
// 2.3.3.1. Let then be x.then.
let then = x.then;
if(typeof then === "function"){ //thenable
var executed = false;
try{
// 2.3.3.3. If then is a function, call it with x as this,
// first argument resolvePromise, and
// second argument rejectPromise,
// where:
then.call(x, (value) => {
// 2.3.3.3.3. If both resolvePromise and rejectPromise are called,
// or multiple calls to the same argument are made,
// the first call takes precedence, and any further calls are ignored.
if(executed){
return;
}
executed = true;
// 2.3.3.3.1. If/when resolvePromise is called with a value y,
// run [[Resolve]](promise, y).
innerResolve(promise, value);
}, (reason) => {
// 2.3.3.3.3. If both resolvePromise and rejectPromise are called,
// or multiple calls to the same argument are made,
// the first call takes precedence, and any further calls are ignored.
if(executed){
return;
}
executed = true;
// 2.3.3.3.2. If/when rejectPromise is called with a reason r,
// reject promise with r.
this._reject(reason);
});
}catch(e){
// 2.3.3.3.4. If calling then throws an exception e,
// 2.3.3.3.4.1. If resolvePromise or rejectPromise have been called, ignore it.
if(executed){
return;
}
// 2.3.3.3.4.2. Otherwise, reject promise with e as the reason.
throw e;
}
}else{
// 2.3.3.4. If then is not a function, fulfill promise with x.
this._fulfill(x);
}
}catch(ex){
// 2.3.3.2. If retrieving the property x.then results in a thrown exception e,
// reject promise with e as the reason.
this._reject(ex);
}
}else{
// 2.3.4. If x is not an object or function, fulfill promise with x.
this._fulfill(x);
}
};
innerResolve(promise, x)
}
function reject(promise, reason){
this._reject(reason);
}
resolve = resolve.bind(this, this); // 經過bind模擬規範中的 [[Resolve]](promise, x) 行爲
reject = reject.bind(this, this);
fn(resolve, reject); // new RookiePromise((resolve, reject) => { ... })
}
/** * 2.1. Promise States * * A promise must be in one of three states: pending, fulfilled, or rejected. * * 2.1.1. When pending, a promise: * 2.1.1.1 may transition to either the fulfilled or rejected state. * 2.1.2. When fulfilled, a promise: * 2.1.2.1 must not transition to any other state. * 2.1.2.2 must have a value, which must not change. * 2.1.3. When rejected, a promise: * 2.1.3.1 must not transition to any other state. * 2.1.3.2 must have a reason, which must not change. * * Here, 「must not change」 means immutable identity (i.e. ===), * but does not imply deep immutability. */
RookiePromise.prototype._fulfill = function(value) {
if(this._state == STATE_PENDING){
this._state = STATE_FULFILLED;
this._value = value;
this._notify(this._callbacks, this._value);
this._errorbacks = [];
this._callbacks = [];
}
}
RookiePromise.prototype._reject = function(reason) {
if(this._state == STATE_PENDING){
this._state = STATE_REJECTED;
this._value = reason;
this._notify(this._errorbacks, this._value);
this._errorbacks = [];
this._callbacks = [];
}
}
RookiePromise.prototype._notify = function(fns, param) {
setTimeout(()=>{
for(var i=0; i<fns.length; i++){
fns[i](param);
}
}, 0);
}
/** * 2.2. The then Method * A promise’s then method accepts two arguments: * promise.then(onFulfilled, onRejected) */
RookiePromise.prototype.then = function(onFulFilled, onRejected) {
// 2.2.7. then must return a promise [3.3].
// promise2 = promise1.then(onFulFilled, onRejected);
//
return new RookiePromise((resolve, reject)=>{
// 2.2.1. Both onFulfilled and onRejected are optional arguments:
// 2.2.1.1. If onFulfilled is not a function, it must be ignored.
// 2.2.1.2. If onRejected is not a function, it must be ignored.
if(typeof onFulFilled == "function"){
this._callbacks.push(function(value){
try{
// 2.2.5. onFulfilled and onRejected must be called as functions (i.e. with no this value)
var value = onFulFilled(value);
resolve(value);
}catch(ex){
// 2.2.7.2. If either onFulfilled or onRejected throws an exception e,
// promise2 must be rejected with e as the reason.
reject(ex);
}
});
}else{
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
// promise2 must be fulfilled with the same value as promise1.
this._callbacks.push(resolve); // 值穿透
}
if(typeof onRejected == "function"){
this._errorbacks.push(function(reason){
try{
// 2.2.5. onFulfilled and onRejected must be called as functions (i.e. with no this value)
var value = onRejected(reason);
resolve(value);
}catch(ex){
// 2.2.7.2. If either onFulfilled or onRejected throws an exception e,
// promise2 must be rejected with e as the reason.
reject(ex);
}
});
}else{
// 2.2.7.4. If onRejected is not a function and promise1 is rejected,
// promise2 must be rejected with the same reason as promise1.
this._errorbacks.push(reject); // 值穿透
}
// 2.2.6. then may be called multiple times on the same promise.
// 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks must
// execute in the order of their originating calls to then.
// 2.2.6.2. If/when promise is rejected, all respective onRejected callbacks must
// execute in the order of their originating calls to then.
if(this._state == STATE_REJECTED){
// 2.2.4. onFulfilled or onRejected must not be called until the
// execution context stack contains only platform code.
this._notify(this._errorbacks, this._value);
this._errorbacks = [];
this._callbacks = [];
}else if(this._state == STATE_FULFILLED){
// 2.2.4. onFulfilled or onRejected must not be called until the
// execution context stack contains only platform code.
this._notify(this._callbacks, this._value);
this._errorbacks = [];
this._callbacks = [];
}
});
};
RookiePromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
};
RookiePromise.resolve = function(value) {
return new RookiePromise((resolve, reject) => resolve(value));
};
RookiePromise.reject = function(reason) {
return new RookiePromise((resolve, reject) => reject(reason));
};
RookiePromise.all = function(values) {
return new Promise((resolve, reject) => {
var result = [], remaining = values.length;
function resolveOne(index){
return function(value){
result[index] = value;
remaining--;
if(!remaining){
resolve(result);
}
};
}
for (var i = 0; i < values.length; i++) {
RookiePromise.resolve(values[i]).then(resolveOne(i), reject);
}
});
};
RookiePromise.race = function(values) {
return new Promise((resolve, reject) => {
for (var i = 0; i < values.length; i++) {
RookiePromise.resolve(values[i]).then(resolve, reject);
}
});
};
module.exports = RookiePromise;
複製代碼
RookiePromise的結構是按照Promise/A+規範中對then、resolve接口的描述組織的;優勢是編碼過程直觀,缺點是innerResolve函數篇幅太長、頭重腳輕,不夠和諧;相信各位能夠寫出更漂亮的版本;
npm install –save promises-aplus-tests
RookiePromise須要額外提供3個靜態接口,供Promise/A+自動測試工具調用;
/** * In order to test your promise library, * you must expose a very minimal adapter interface. * These are written as Node.js modules with a few well-known exports: * * resolved(value): creates a promise that is resolved with value. * rejected(reason): creates a promise that is already rejected with reason. * deferred(): creates an object consisting of { promise, resolve, reject }: * promise is a promise that is currently in the pending state. * resolve(value) resolves the promise with value. * reject(reason) moves the promise from the pending state to the rejected state, * with rejection reason reason. * * https://github.com/promises-aplus/promises-tests */
var RookiePromise = require('./RookiePromise.js');
RookiePromise.resolved = RookiePromise.resolve;
RookiePromise.rejected = RookiePromise.reject;
RookiePromise.deferred = function() {
let defer = {};
defer.promise = new RookiePromise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
module.exports = RookiePromise
複製代碼
npx promises-aplus-testsRookiePromiseTestAdapter.js > log.txt
《ES6 標準入門(第三版)》
《深刻理解ES6》
MDN(Promise):
developer.mozilla.org/en-US/docs/…
Promise 示例(Promise 和 XMLHttpRequest 加載圖像):
github.com/mdn/js-exam…
States and Fates:
github.com/domenic/pro…
Promise/A+規範文檔:
github.com/promises-ap…
Promise/A+規範測試集:
github.com/promises-ap…
符合Promise/A+規範的一些開源實現:
github.com/promises-ap…
社區以及公衆號發佈的文章,100%保證是咱們的原創文章,若是有錯誤,歡迎你們指正。
文章首發在WebJ2EE公衆號上,歡迎你們關注一波,讓咱們你們一塊兒學前端~~~
再來一波號外,咱們成立WebJ2EE公衆號前端吹水羣,你們不論是看文章仍是在工做中前端方面有任何問題,咱們均可以在羣內互相探討,但願可以用咱們的經驗幫更多的小夥伴解決工做和學習上的困惑,歡迎加入。