小哥哥、小姐姐,大家好,請把手伸出來,我給大家點東西。javascript
咱們都知道js是單線程語言,這就致使了會有同步異步的概念。所謂同步,就是指腳本解釋器在解析代碼時,從上往下一行一行解釋,第一行解釋不完,就不去解釋第二行。所謂異步,就是指,當解釋到某一行中,發現有異步方法(好比settimeout、ajax、DOM點擊事件等等),解釋器不會去等待異步方法執行完,再往下解釋。而是,將異步任務放到任務隊列,當全部的同步代碼所有執行完,也就是主線程沒有可執行的代碼了,就回去任務隊列拿出以前遇到的異步任務,執行,執行完畢後再去任務隊列調取下一個任務,如此循環。java
一圖勝千言node
一次只能服務一我的,請排好隊。誰事兒多也沒辦法,後面只能等着。 三我的同時吃飯,這是異步。若是是同步的話,只能一我的吃完下一我的再吃。相信你們對回調函數已經不陌生了,函數A做爲參數被傳遞到函數B裏,那麼函數A就是回調函數。有什麼用呢?請看代碼ajax
let doSomething = () => { console.log('do something') }
setTimeout(doSomething, 500);
console.log('a');
複製代碼
聲明瞭一個
doSomething
函數,並做爲第一個參數傳遞給了setTimeout
函數,setTimeout
函數會在合適的時機執行它。達到了異步編程的目的。這種方式用處有不少,node.js
有大部分api
都是經過回調來實現異步編程的。編程
promise
寫法回調函數這種形式有一個缺點,那就是若是異步任務比較多的話,而且多任務執行有前後順序,那麼回調函數很容易就造成多層嵌套。以下:api
function doA() { }
function doB() { }
function doC() { }
function doD() { }
doA(function () {
doB(function () {
doC(function () {
doD(function () {
})
})
})
})
複製代碼
當改用promise後,瞬間清爽了許多。數組
new Promise(function(resolve,reject){
resolve();//在合適的時機出發resolve
})
.then(doA,null)
.then(doB,null)
.then(doC,null)
.then(doD,null)
複製代碼
這也是這篇文章的重點講解內容,一會我會一步一步按照規範編寫一個promise庫。完全搞懂promise。promise
generator
寫法function* gen() {
let a = yield doA();
let b = yield doB();
let c = yield doC();
let d = yield doD();
}
let it = gen();//it是一個迭代器
it.next();//{ value: undefined, done: false }
it.next();{ value: undefined, done: false }
it.next();{ value: undefined, done: false }
it.next();{ value: undefined, done: false }
it.next();{ value: undefined, done: true }
複製代碼
能夠看到,generator
函數有另一個功能,那就是能夠暫停,不像普通函數,只要一執行,那就會一口氣執行完。bash
async
+ await
寫法async function doSomething() {
await doA();
await doB();
await doC();
await doD();
}
doSomething();
複製代碼
是否是發現,這種寫法更加簡潔,就像在寫同步代碼同樣。異步
先來看Promise
的用法,而後根據用法一步步編寫Promise
類
let p1 = new Promise(function (resolve, reject) {
})
p1.then(function (data) {
console.log(data)
}, function (err) {
console.log(err)
})
複製代碼
在實例化一個Promise
時,傳入一個函數做爲參數,該函數接受兩個參數,分別爲resolve
,reject
,而後按照Promise/A+規範一個Promise類應該包含以下狀態
status
value
reason
onResolvedCallbacks
onRejectedCallbacks
咱們很容易就寫出了Promise
的原型。代碼以下:function Promise(executor) {
let self = this;
self.status = 'pending';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve() { }
function reject() { }
executor(resolve, reject);
}
Promise.prototype.then = function (onFulfilled, onRejected) {
}
複製代碼
接下來咱們一個一個方法去攻破。
resolve
、 reject
方法resolve
方法須要完成的事情是:
resolved
self.value
的值self.onResolvedCallbacks
,並一一執行。reject
方法須要完成的事情是
rejectd
self.reason
的值self.onRejectedCallbacks
,並一一執行。 代碼以下:function resolve(value) {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(item => item(value))
}
function reject(reason) {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(item => item(reason))
}
複製代碼
then
方法的做用是收集到成功、失敗的回調函數,將他們分別添加到成功和失敗的數組中。也就是代碼中,咱們須要將onFulfilled
添加到self.onResolvedCallbacks
裏,將onRejected
添加到self.onRejectedCallbacks
裏。
Promise.prototype.then = function (onFulfilled, onRejected) {
this.onResolvedCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
複製代碼
寫到這裏,這個Promise
其實已經能夠用了,不過還有一個潛在的問題。那就是,當resolve
方法被同步調用時,經過then
方法加入到隊列的函數沒有被執行。只有resolve
被異步調用時纔會被執行,爲何呢。由於這裏的then
是同步的,resolve
也被同步調用的話,那確定是,先執行resolve
後執行then
,換句話說就是,先執行回調,後添加回調,這不是咱們想看到的,要達到先添加回調,後執行回調的效果,咱們稍做修改。
function resolve(value) {
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(item => item(value))
}
});
}
function reject(reason) {
setTimeout(function () {
if (self.status == 'pending') {
self.value = value;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item => item(value));
}
});
}
複製代碼
這裏加入了狀態判斷,由於當Promise
的狀態一旦肯定,就不能更改,因此狀態只能是pending
時,resolve
和reject
才生效。
先看一下Promise/A+規範中對then
方法的返回值描述。
then
方法必須返回一個
promise
,以實現鏈式調用。如今咱們須要關注的是,調用
then
方法時,傳入的第一個參數(
onFulfilled
)的返回值問題。若是是一個普通值,那咱們就把它繼續傳遞下去,傳遞給then方法返回的
promise
裏;若是是一個新的
promise
的話,那就須要將新的
promise
和
then
方法返回的
promise
關聯起來。 具體如何關聯,我們慢慢來,這裏有點繞,我先上張圖看圖說話。
圖中有部分代碼只須要注意三個
promise,
p1
、
x
、
p2
,爲了避免形成混淆,這三個東西我一次標到了圖的左部分,他們是對應的。請你們先明白一句話,而後咱們開始說。
調用promise的resolve方法,會執行該promise的then函數的第一個參數
(這裏我那resolve舉例,reject道理同樣,就不贅述了。)
看圖,請看圖。
調用p1的resolve方法,那麼a就會被執行。
調用p2的resolve方法,那麼c就會被執行。
也就是說,你想執行a或者b或者c或者d,那麼你得找到它屬於哪一個promise,例如:圖中,a b 屬於p1,c d屬於p2.這個關係必需要明確。
假設你已經理解了上面的話,如今咱們面臨的問題來了。
c d 原本是屬於p2的,執行仍是不執行也得看p2調不調用resolve、reject。 如今要讓c d執不執行不看p2了,得看x。爲何要看x,由於x這個回調函數是用戶傳遞的,用戶的意思是:我讓這個回調返回一個promise,而後繼續使用then方法添加成功或失敗的回調,並且這兩個回調啥時候執行,得看我返回的那個promise。 反應到圖中就是這個意思:c d什麼時候執行,看x什麼時候調用resolve、reject。
但願你理解了。...繼續
破解方法:將p2的resolve、reject放入到x的then方法裏。 解釋一下:x的resolve、reject是暴露給用戶的,也就是說,這兩個方法的執行權在用戶手裏,當用戶執行resolve時,其實就執行了x的then方法的第一個參數,而x的then方法的第一個參數正好是p2的resolve,p2的resolve就被執行了,p2的resolve一執行,那麼c就被執行了。就實現了x的resolve、reject控制着c d的執行與否。
說了這麼多,上代碼吧仍是,改造後的then方法以下,加入了狀態判斷,錯誤捕獲
Promise.prototype.then = function (onFulfilled, onRejected) {
let promise2;
let self = this;
if (self.status === 'resolve') {
promise2 = Promise(function (resolve, reject) {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
})
}
if (self.satus === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function (value) {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push(function (reason) {
try {
let x = onRejected(reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
return promise2;
}
複製代碼
再把resolvePromise
方法寫一下,由於多個地方用到了,因此就單獨封裝了。
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循環引用'));
}
let then, called;
if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
try {
then = x.then;
if (typeof then == 'function') {
then.call(x, function (y) {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, function (r) {
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
複製代碼
到這裏,咱們寫的promise已經支持鏈式調用了。我但願閱讀本文的你,先去讀懂那張圖,必定要看懂,知道本身在幹什麼,就是思路要清晰,而後再去寫代碼。我剛接觸的時候,就是一步步去捋思路,而後輔助畫圖去理解。用了好幾天才弄懂。
catch
、all
、race
、Promise.resolve()
、Promise.reject()
相比較then方法,這幾個方法就輕鬆許多了。直接上代碼了,我會把註釋寫到代碼裏邊 catch
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);//原來這麼簡單
}
複製代碼
all
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let result = [];//結果集
let count = 0;//計數器,用來記錄promise有沒有執行完
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);//計數器知足條件時,觸發resolve
}
}, function (err) {
reject(err);
});
}
});
}
複製代碼
race
// 只要有一個promise成功了 就算成功。若是第一個失敗了就失敗了
Promise.race = function (promises) {
return new Promise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
})
}
複製代碼
Promise.resolve()、Promise.reject()
// 生成一個成功的promise
Promise.resolve = function (value) {
return new Promise(function (resolve, reject) {
resolve(value);
})
}
// 生成一個失敗的promise
Promise.reject = function (reason) {
return new Promise(function (resolve, reject) {
reject(reason);
})
}
複製代碼
Promise
的語法糖Promise.deferred = Promise.defer = function () {
var defer = {};
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
})
return defer;
}
複製代碼
看一個例子
let fs = require('fs');
let Promise = require('./promise');
function read() {
// 好處就是解決嵌套問題
// 壞處錯誤處理不方便了
let defer = Promise.defer();
fs.readFile('./2.promise.js/a.txt','utf8',(err,data)=>{
if(err)defer.reject(err);
defer.resolve(data)
});
return defer.promise;
}
read().then(data=>{
console.log(data);
});
複製代碼
說了不少,但願大家理解了,若是文中有錯誤或者你們有不懂的地方,歡迎留言。