為什麼還要實作promise???隻因之前方案不夠好,有的不符合MDN的描述、有的不符合promise/A+規範、有的不能相容同步和異步兩種内部函數的使用形式。故此,還是自己動手、豐衣足食,實作了PromiseDemo,以下是測試用例及實作源碼。
以下測試用例均已認證:
// 用例1:同步情況下resolve
new PromiseDemo(function(resolve, reject){
resolve('同步')
}).then(function(res) {
console.log(res); // 同步
});
複制代碼
// 用例2:resolve為promise時
new PromiseDemo(function(resolve, reject){
resolve(new PromiseDemo(function(reso) {reso('resolve-promose')}))
}).then(function(res) {
console.log(res); // resolve-promose
});
複制代碼
// 用例3:錯誤捕捉
new PromiseDemo(function(resolve, reject){
throw new Error('錯誤');
resolve(123)
}).then(function(res) {
console.log(res);
}).catch(function(err) {
console.log(err);
});
// Error: 錯誤
// at <anonymous>:2:9
// at new PromiseDemo (<anonymous>:9:13)
// at <anonymous>:1:1
複制代碼
// 用例4:異步resolve
new PromiseDemo(function(resolve, rej) {
setTimeout(function() {
resolve(1);
}, 10)
}).then(function(res) {
console.log(res); // 1
}, function(err) {
console.log(err);
});
複制代碼
// 用例5:all方法
PromiseDemo.all([
fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),
fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),
]).then(function(res) {
console.log(res); // [" Vue.js v2.5.16", "React v16.14.0"]
});
複制代碼
// 用例6:race方法請求不同資源
PromiseDemo.race([
fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),
fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))
]).then(function(res) {
console.log(res); // Vue.js v2.5.16
});
複制代碼
// 用例7:race方法請求不同資源
PromiseDemo.race([
fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),
fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))
]).then(function(res) {
console.log(res); // jQuery v3.4.1
});
複制代碼
PromiseDemo實作源碼
感興趣的同學,建議觀碼順序:
1.constructor
2.resolve
3.then
4.reject
5.catch
6.finally
7.all
class PromiseDemo {
/** 構造函數
* 初始化then事件隊列、catch事件隊列、狀态status
* 執行參數fn,傳入resolve和reject
*/
constructor(fn) {
if (fn && typeof fn !== 'function') throw new Error(`Parameter is not a function`);
this.thenQue = []
this.catchQue = []
this.status = '<pending>';
try {
fn && fn(PromiseDemo.resolve.bind(this), PromiseDemo.reject.bind(this));
} catch(e) {
PromiseDemo.reject.bind(this)(e);
}
}
/** resolve函數
* 主要作用為執行thenQue隊列中的函數
* 如果狀态為fulfilled,則不再執行
* 如果resolve的參數不是PromiseDemo執行個體,則正常執行,這也是我們常用的場景
* 如果resolve的參數是PromiseDemo執行個體,則将執行個體内部的resolve值或者then内的函數傳回值暴露出來,連接配接上外部的resolve執行,這樣可以接着用外部的then函數隊列,依次執行。 * 如果resolve一開始調用時,沒有值,則傳回一個PromiseDemo執行個體,因為存在用法Promise.resolve().then()
* 函數内使用了setTimeout,是為了将後續邏輯加入下一個宏任務,此時,then将優先執行,提前将函數邏輯加入thenQue隊列
* 注意,promise是微任務,此處用setTimeout是為了實作promise效果,因為浏覽器環境下,除了mutation和promise外,沒有可以異步的函數了。
*/
static resolve(data) {
if (this.status === '<fulfilled>') return;
this.value = data;
const isInstance = data instanceof PromiseDemo;
if (!isInstance) {
setTimeout(() => {
try {
this.thenQue.forEach((item) => {
this.value = item(this.value);
});
this.thenQue = [];
this.status = '<fulfilled>';
} catch (e) {
this.status = '<rejected>';
PromiseDemo.reject.bind(this)(e);
}
});
} else {
data.then((res) => {
PromiseDemo.resolve.bind(this)(res);
}).catch((err) => {
PromiseDemo.reject.bind(this)(err);
});
}
if (!data) {
return new PromiseDemo();
}
}
/** reject函數
* 主要作用是執行catchQue事件隊列中的catch事件函數
*/
static reject(err) {
if (this.status === '<rejected>') return;
this.error = err;
let count;
setTimeout(() => {
try {
this.catchQue.forEach((item, index) => {
count = index;
this.error = item(this.error);
});
this.catchQue = [];
this.status = '<rejected>';
} catch (e) {
this.catchQue = this.catchQue.slice(count+1);
PromiseDemo.reject.bind(this)(e);
}
});
if (!err) {
return new PromiseDemo();
}
}
/** then函數
* 主要作用為将then内的函數全部收集起來,組成then事件隊列thenQue
*/
then(onResolve, onReject) {
if (typeof onReject === 'function') {
this.catchQue.push(onReject);
}
typeof onResolve === 'function' && this.thenQue.push(onResolve);
return this;
}
/** catch函數
* 主要作用為将catch内的函數全部收集起來,組成catch事件隊列catchQue
*/
catch(fn) {
this.catchQue.push(fn);
return this;
}
/** finally函數
* 将fn推入隊列,無論事件隊列thenQue執行,還是catchQue執行,最後都可以執行到
*/
finally(fn) {
this.thenQue.push(fn);
this.catchQue.push(fn);
}
/** all函數
* 參數為數組,數組每一項都是PromiseDemo的執行個體
* 對每項添加then方法,則當執行到then内部方法時,判斷是否全部promise都已執行完,若都已執行完畢,則整體resolve
*/
static all(arr) {
const resArr = [];
const length = arr.length;
let resCount = 0;
let that;
try {
arr.forEach(function(item, index) {
item.then(function(res) {
resArr[index] = res;
resCount++;
if (resCount === length) {
PromiseDemo.resolve.bind(that)(resArr);
}
})
});
} catch (e) {
PromiseDemo.reject.bind(that)(e);
}
that = new PromiseDemo();
return that;
}
/** race函數
* 隻要有一個執行完畢,則直接整體resolve,當另一個也執行到resolve時,因status已發生改變,則不會再向下執行
*/
static race(arr) {
let that;
try {
arr.forEach(function(item, index) {
item.then(function(res) {
PromiseDemo.resolve.bind(that)(res);
})
});
} catch (e) {
PromiseDemo.reject.bind(that)(e);
}
that = new PromiseDemo();
return that;
}
}複制代碼