前言
Promise 是非同步程式設計的一種解決方案: 從語法上講,promise是一個物件,從它可以獲取非同步操作的訊息;從本意上講,它是承諾,承諾它過一段時間會給你一個結果。 promise有三種狀態:pending(等待態),fulfiled(成功態),rejected(失敗態);狀態一旦改變,就不會再變。創造promise例項後,它會立即執行。
編寫符合promiseA+規範的promise實現
在實現之前,可以先看一下Promise A plus規範
1. 建立promise建構函式
這裡先實現promise最基本的功能:promise建立後立即執行;在then時執行相應的函式;捕獲錯誤立即變成reject態。
// promise裡只有一個引數,叫executor(執行器)
function Promise(executor) {
let self = this;
self.status = 'pending';//等待態
self.value = undefined;//預設成功的值
self.err = undefined;//預設失敗的值
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
}
}
function reject(err) {
if (self.status === 'pending') {
self.status = 'rejected';
self.err = err;
}
}
try {//捕獲時發生異常,直接變成reject態,丟擲錯誤
executor(resolve, reject);//promise例項建立後,立即執行
} catch (error) {
reject(error);
}
}
//在prototype上定義then例項方法
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
if (self.status === 'resolved') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.err);
}
}
複製程式碼
這裡我們先測試一下我們的Promise
這裡便實現了基本功能,前面說過Promise 是非同步程式設計的一種解決方案; 我們加個非同步邏輯執行一下:
2. Promise非同步呼叫
我們都知道非同步程式碼並不會立即執行,這時既不是resolved也不是rejected,而是pending。
在之前的狀態判斷裡面,正好丟了一個pending狀態。
OK,這時需要在then裡判斷當status為pending時,先將onFulfilled, onRejected存入陣列裡,當status改變時,再遍歷陣列讓裡面的函式依次執行,看程式碼。
(1)申明兩個存放onFulfiled(),onRejected()的陣列
function Promise(resolver) {
let self = this;
self.status = 'pending';//等待態
self.value = undefined;//預設成功的值
self.err = undefined;//預設失敗的值
self.onResolvedCallbacks = []; // 存放then成功的回撥
self.onRejectedCallbacks = []; // 存放then失敗的回撥
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(fn=>{//呼叫resolve時,依次執行陣列裡的函式
fn();
})
}
}
function reject(err) {
if (self.status === 'pending') {
self.status = 'rejected';
self.err = err;
self.onRejectedCallbacks.forEach(fn=>{
fn();
})
}
}
try {//捕獲時發生異常,直接丟擲錯誤
resolver(resolve, reject);//promise例項建立後,立即執行它的方法
} catch (error) {
reject(error)
}
}
複製程式碼
(2)接著在then方法裡新增pending的判斷
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
if (self.status === 'resolved') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.err);
}
if(self.status==='pending'){// 此時沒resolved,也沒rejectd
self.onResolvedCallbacks.push(()=>{
onFulfilled(self.value);
});
self.onRejectedCallbacks.push(()=>{
onRejected(self.err);
})
}
}
複製程式碼
再看剛剛的非同步邏輯
1s後就執行成功了,是不是很神奇,再看下面:
3. Promise鏈式呼叫
(1)規範裡說在同一個promise裡then可以被多次呼叫。
(2)jquery能實現鏈式呼叫靠的是返回this,而promise不能返回this,規範裡又說它返回的是一個新的Promise例項 (注意,不是原來那個Promise例項);
在then裡新建一個promise2併為每一個狀態包一個Promise
這裡就返回了一個新的promise2,在promise2裡也需要呼叫resolve或者reject;這裡申明一個 x 來儲存上一次then的返回值 規範裡說只要上一次then有返回值,下一次then一定呼叫成功態resolve(x) 再來看看規範,規範裡說道
(1)x可能是一個promise;
(2)可能是一個物件或者方法;
(3)也有可能是一個普通的值。
這時需要一個方法來處理x
3.1 對onFulfilled和onRejected的返回值進行處理
於是引入一個處理方法resolvePromise(promise2, x, resolve, reject); 這裡四個引數分別是
- Promise2是我們返回的新的promise
- x是上一次then的返回值
- resolve是成功的方法
- reject是失敗的方法
這裡需要注意一下,有些人寫的promise可能會既呼叫成功,又呼叫失敗,如果兩個都呼叫先呼叫誰另一個就忽略掉。 在resolvePromise(promise2, x, resolve, reject)裡增加一個判斷called表示是否呼叫過成功或者失敗,看程式碼:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {//promise2和x不能相同
return reject(new TypeError('迴圈引用了'))
}
let called;// 表示是否呼叫過成功或者失敗
//這裡對x的型別進行判斷
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try { // 判斷x是不是promise,如果x是物件並且x的then方法是函式我們就認為他是一個promise
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
if (called) return
called = true
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失敗
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 說明是一個普通值1
resolve(x); // 表示成功了
}
}
複製程式碼
相應的將前面的程式碼進行一些更改
這時我們就實現了promise的鏈式呼叫。4. 值的穿透問題
如果在then中什麼都不傳,值會穿透到最後呼叫的時候;
這時需要在then裡給onFulfilled和onRejected寫一個預設的函式
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
throw err;//這裡需要丟擲錯誤,不能return err,否則會在下一次呼叫成功態
}
複製程式碼
5. then的非同步實現
規範裡要求,所有的onFulfilled和onRejected都要確保非同步執行
這裡以resolve為例,寫一個setTimeout():
6. defer語法糖
在使用promise的過程中,我們都需要先new Promise(),比如說:
function read() {
let fs = require('fs');
let promise = new Promise(function(resolve,reject){
fs.readFile('./1.txt','utf8',function(err,data){
if(err) reject(err);
resolve(data);
})
});
return promise
}
複製程式碼
在Promise中,它為我們提供了一個語法糖Promise.defer,用Promise.defer只需這樣寫:
function read() {
let defer = Promise.defer()
require('fs').readFile('.//1.txt', 'utf8', function (err, data) {
if(err) defer.reject(err);
defer.resolve(data);
})
return defer.promise;
}
複製程式碼
為此,再為我們的Promise加一個defer方法:
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
複製程式碼
在這裡,我們基本實現了一個比較完整的promise;當然Promise還有許多靜態方法,還有js的非同步發展史,這些可以在下一次進行討論。 完整程式碼:
// promise裡只有一個引數,叫executor(執行器)
function Promise(executor) {
let self = this;
self.status = 'pending';//等待態
self.value = undefined;//預設成功的值
self.err = undefined;//預設失敗的值
self.onResolvedCallbacks = []; // 存放then成功的回撥
self.onRejectedCallbacks = []; // 存放then失敗的回撥
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
});
}
}
function reject(err) {
if (self.status === 'pending') {
self.status = 'rejected';
self.err = err;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
});
}
}
try {//捕獲時發生異常,直接變成reject態,丟擲錯誤
executor(resolve, reject);//promise例項建立後,立即執行
} catch (error) {
reject(error);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {//promise2和x不能相同
return reject(new TypeError('迴圈引用了'))
}
let called;// 表示是否呼叫過成功或者失敗
//這裡對x的型別進行判斷
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try { // 判斷x是不是promise,如果x是物件並且x的then方法是函式我們就認為他是一個promise
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
if (called) return
called = true
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失敗
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 說明是一個普通值1
resolve(x); // 表示成功了
}
}
//在prototype上定義then例項方法
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
throw err;//這裡需要丟擲錯誤,不能return err,否則會在下一次呼叫成功態
}
let self = this;
let promise2; //返回的promise
if (self.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(self.err);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
// 當呼叫then時可能沒成功 也沒失敗
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
// 此時沒有resolve 也沒有reject
self.onResolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
});
self.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRejected(self.err);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
})
}
return promise2;
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
module.exports = Promise;
複製程式碼
7.Promise測試
npm i -g promises-aplus-tests
promises-aplus-tests Promise.js
複製程式碼