上篇博客寫着寫着沒動力,而後就拖了一個月。javascript
如今打算在一週內完成。html
這篇講Promise
和co
的原理+實現。java
Promise的規範有不少,其中ECMAScript 6
採用的是Promises/A+.
想要了解更多最好仔細讀完Promises/A+
,順便說下Promise
是依賴於異步實現。node
而在JavaScript
中有兩種異步宏任務macro-task
和微任務micro-task
.react
在掛起任務時,JS 引擎會將全部任務按照類別分到這兩個隊列中,首先在 macrotask 的隊列(這個隊列也被叫作 task queue)中取出第一個任務,執行完畢後取出 microtask 隊列中的全部任務順序執行;以後再取 macrotask 任務,周而復始,直至兩個隊列的任務都取完。git
常見的異步代碼實現github
macro-task
: script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering面試
micro-task
: process.nextTick, Promises(原生 Promise), Object.observe(api已廢棄), MutationObservertypescript
以上的知識摘查於Promises/A+
順便說下一個前段時間看到的一個js
面試題api
setTimeout(function() { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(3); }).then(function() { console.log(4); }); console.log(5);
在node v8.13使用原生的Promise
是: "2 3 5 4 1"。
可是若是使用bluebird
的第三方Promise
就是: "2 3 5 1 4"。
這個緣由是由於bluebird
在這個環境下優先使用setImmediate
代碼。
而後再看上面的代碼執行順序.
第一次總體代碼進入macro-task
。micro-task
爲空。
macro-task
執行總體代碼,setTimeout
加入下一次的macro-task
。Promise
執行打出2 3
,then
加入micro-task
, 最後打出5
。
micro-task
執行then
被執行因此打出4
。
從新執行macro-task
因此打出1
可是在bluebird
裏的then
使用setImmediate
因此上面的步驟會變成:
步驟2then
在setTimeout
後加入macro-task
。
步驟3會由於micro-task
爲空跳過。
步驟4 執行setTimeout
,then
打出1 4
。
new Promise(func:(resolve, reject)=> void 0), 這裏的func方法被同步執行。
Promise
會有三種狀態PENDING(執行)
,FULFILLED(執行成功)
,REJECTED(執行失敗)
。
在resolve
,reject
均未調用且未發生異常時狀態爲PENDING
。
resolve
調用爲FULFILLED
,reject
調用或者發生異常爲REJECTED
。
在給Promise
實例調用then(callFulfilled, callRejected)
來設置回調,狀態不爲PENDING
時會根據狀態調用callFulfilled
和callRejected
。
then
須要返回一個新的Promise
實例.
狀態爲PENDING
則會把callFulfilled
和callRejected
放入當前Promise
實例的回調隊列中,隊列還會存儲新的Promise
實例。
在狀態改變爲FULFILLED
或REJECTED
時會回調當前Promise
實例的隊列。
Promise Api
下面是Promise
的全部開放api這裏爲了區分與原生的因此類名叫Appoint
。
順便爲了學習typescript
class Appoint { public constructor(resolver: Function){}; public then(onFulfilled, onRejected): Appoint {}; public catch(onRejected): Appoint {}; public static resolve(value): Appoint {}; public static reject(error): Appoint {}; public static all(iterable: Appoint[]): Appoint {}; public static race(iterable): Appoint {}; }
function INTERNAL() {} enum AppointState { PENDING, FULFILLED, REJECTED, } class Appoint { public handled: boolean; public value: any; public queue: QueueItem[]; private state: AppointState; public constructor(resolver: Function){ if (!isFunction(resolver)) { throw new TypeError("resolver must be a function"); } // 設置當前實例狀態 this.state = AppointState.PENDING; this.value = void 0; // 初始化回調隊列 this.queue = []; // true表明沒有設置then this.handled = true; if (resolver !== INTERNAL) { // 安全執行傳入的函數 safelyResolveThen(this, resolver); } } // state 的getset public setState(state: AppointState) { if (this.state === AppointState.PENDING && this.state !== state) { this.state = state; } } public getState(): AppointState { return this.state; } }
function safelyResolveThen(self: Appoint, then: (arg: any) => any) { let called: boolean = false; try { then(function resolvePromise(value: any) { if (called) { return; } // 保證doResolve,doReject只執行一次 called = true; // 改變當前狀態以及調用回調隊列 doResolve(self, value); }, function rejectPromise(error: Error) { if (called) { return; } // 同上 called = true; doReject(self, error); }); } catch (error) { // 特別捕捉錯誤 if (called) { return; } called = true; doReject(self, error); } }
/** * 若是value不是一個Promise,對Promise調用回調隊列。 * 若是是就等待這個Promise回調 */ function doResolve(self: Appoint, value: any) { try { // 判斷是否爲Promise const then = getThen(value); if (then) { // safelyResolveThen(self, then); } else { // 改變狀態 self.setState(AppointState.FULFILLED); self.value = value; // 調用回調隊列 self.queue.forEach((queueItem) => { queueItem.callFulfilled(value); }); } return self; } catch (error) { return doReject(self, error); } } /** * 調用回調隊列 */ function doReject(self: Appoint, error: Error) { // 改變狀態 self.setState(AppointState.REJECTED); self.value = error; if (self.handled) { // 未設置then回調 asap(() => { // 建立一個異步任務保證代碼都執行了再判斷 if (self.handled) { if (typeof process !== "undefined") { // node 環境下觸發unhandledRejection事件 process.emit("unhandledRejection", error, self); } else { // 瀏覽器環境直接打印便可 console.error(error); } } }); } self.queue.forEach((queueItem) => { queueItem.callRejected(error); }); return self; } /** * 判斷是否爲Object且有then屬性的方法, * 有返回這個方法的綁定this * 這種判斷方式會發生若是 * resolve({ then: () => {} })的話就會丟失下次的then * 原生的Promise也是相同 */ function getThen(obj: any): Function { const then = obj && obj.then; if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then){ return then.bind(obj); } return null; }
/** * 使用micro-task的異步方案來執行方法 */ function asap(callback) { if (typeof process !== "undefined") { process.nextTick(callback); } else { const BrowserMutationObserver = window.MutationObserver || window.WebKitMutationObserver let iterations = 0; const observer = new BrowserMutationObserver(callback); const node: any = document.createTextNode(""); observer.observe(node, { characterData: true }); node.data = (iterations = ++iterations % 2); } } /** * 異步執行then,catch */ function unwrap(promise: Appoint, func: Function, value: any): void { asap(() => { let returnValue; try { // 執行then,catch回調得到返回值 returnValue = func(value); } catch (error) { // 發生異常直接觸發該promise的Reject return doReject(promise, error); } if (returnValue === promise) { // 執行then,catch回調返回值不能爲promise本身 doReject(promise, new TypeError("Cannot resolve promise with itself")); } else { // then,catch回調成功,直接觸發該promise的Resolve doResolve(promise, returnValue); } }); } public then<U>( onFulfilled?: (value?: any) => U, onRejected?: (error?: any) => U, ): Appoint { // 直接無視來作到值穿透 if (!isFunction(onFulfilled) && this.state === AppointState.FULFILLED || !isFunction(onRejected) && this.state === AppointState.REJECTED ) { return this; } // 新建一個空的 const promise = new Appoint(INTERNAL); // 當前實例已經被設置then if (this.handled) { this.handled = false; } if (this.getState() !== AppointState.PENDING) { // 當前實例已經結束運行直接根據狀態獲取要回調的方法 const resolver = this.getState() === AppointState.FULFILLED ? onFulfilled : onRejected; // 異步執行resolver,若是成功會觸發新實例的then,catch unwrap(promise, resolver, this.value); } else { // 若是Promise的任務還在繼續就直接把生成一個QueueItem // 並設置好新的Promise實例 this.queue.push(new QueueItem(promise, onFulfilled, onRejected)); } // 返回新生成的 return promise; } public catch<U>(onRejected: (error?: any) => U): Appoint { return this.then(null, onRejected); }
export class QueueItem { // 每次then|catch生成的新實例 public promise: Appoint; // then回調 public callFulfilled: Function; // catch回調 public callRejected: Function; constructor(promise: Appoint, onFulfilled?: Function, onRejected?: Function { this.promise = promise; if (isFunction(onFulfilled)) { this.callFulfilled = function callFulfilled(value: any) { // 異步執行callFulfilled,後觸發新實例的then,catch unwrap(this.promise, onFulfilled, value); }; } else { this.callFulfilled = function callFulfilled(value: any) { // 沒有設置callFulfilled的話直接觸發新實例的callFulfilled /* 例以下面這種代碼一次catch可是沒有then而下面代碼中的then是catch返回的新實例 因此須要直接 new Promise(() => { }) .catch(() => {}) .then() */ doResolve(this.promise, value); }; } if (isFunction(onRejected)) { this.callRejected = function callRejected(error: Error) { // 異步執行callRejected,後會觸發新實例的then,catch unwrap(this.promise, onRejected, error); }; } else { this.callRejected = function callRejected(error: Error) { // 沒有設置callRejected的話直接觸發新實例的callRejected doReject(this.promise, error); }; } } }
export function isFunction(func: any): boolean { return typeof func === "function"; } export function isObject(obj: any): boolean { return typeof obj === "object"; } export function isArray(arr: any): boolean { return Object.prototype.toString.call(arr) === "[object Array]"; }
public static resolve(value: any): Appoint { if (value instanceof Appoint) { return value; } return doResolve(new Appoint(INTERNAL), value); } public static reject(error: any): Appoint { if (error instanceof Appoint) { return error; } return doReject(new Appoint(INTERNAL), error); }
/** * 傳入一個Promise數組生成新的Promise全部Promise執行完後回調 */ public static all(iterable: Appoint[]): Appoint { const self = this; if (!isArray(iterable)) { return this.reject(new TypeError("must be an array")); } const len = iterable.length; let called = false; if (!len) { return this.resolve([]); } const values = new Array(len); let i: number = -1; const promise = new Appoint(INTERNAL); while (++i < len) { allResolver(iterable[i], i); } return promise; function allResolver(value: Appoint, index: number) { self.resolve(value).then(resolveFromAll, (error: Error) => { if (!called) { called = true; doReject(promise, error); } }); function resolveFromAll(outValue: any) { values[index] = outValue; if (index === len - 1 && !called) { called = true; doResolve(promise, values); } } } } /** * 與all相似可是,只要一個Promise回調的就回調 */ public static race(iterable: Appoint[]): Appoint { const self = this; if (!isArray(iterable)) { return this.reject(new TypeError("must be an array")); } const len = iterable.length; let called = false; if (!len) { return this.resolve([]); } const values = new Array(len); let i: number = -1; const promise = new self(INTERNAL); while (++i < len) { resolver(iterable[i]); } return promise; function resolver(value: Appoint) { self.resolve(value).then((response: any) => { if (!called) { called = true; doResolve(promise, response); } }, (error: Error) => { if (!called) { called = true; doReject(promise, error); } }); } }
不使用co的話不停的then,和callback明顯會很難受。
function callback (null, name) { console.log(name) } new Promise(function(resolve) { resolve('<h1>test</h1>') }).then(html => { setTimeout(function(){ callback('test' + html) }, 100) })
改用co異步代碼感受和寫同步代碼同樣。
const co = require("co") co(function *test() { const html = yield new Promise(function(resolve) { resolve('<h1>test</h1>') }) console.log('--------') const name = yield function (callback) { setTimeout(function(){ callback(null, 'test' + html) }, 100) } return name }).then(console.log)
這裏不得不說下Generator了,直接看執行效果吧:
function *gen() { const a = yield 1 console.log('a: ', a) const b = yield 2 console.log('b: ', b) return 3 } const test = gen() test.next() // Object { value: 1, done: false } test.next(4) // a: 4\n Object { value: 2, done: false } test.next(5) // b: 5\n Object { value: 3, done: true }
很明顯除了第一次next
的參數都會賦值到上一次的yield
的左邊變量。
最後一次的next
返回的value
是return
的值,其它都是yield
右邊的變量。
而co
就是經過不停的next
獲取到支持的異步對象回調後把值放到下次的next
中從而達到效果。
const slice = Array.prototype.slice; const co: any = function co_(gen) { const ctx = this; const args = slice.call(arguments, 1); return new Promise(function _(resolve, reject) { // 把傳入的方法執行一下並存下返回值 if (typeof gen === "function") { gen = gen.apply(ctx, args); } // 1. 傳入的是一個方法經過上面的執行得到的返回值, // 若是不是一個有next方法的對象直接resolve出去 // 2. 傳入的不是一個方法且不是一個next方法的對象直接resolve出去 if (!gen || typeof gen.next !== "function") { return resolve(gen); } // 執行,第一次next不須要值 onFulfilled(); /** * @param {Mixed} res * @return {null} */ function onFulfilled(res?: any) { let ret; try { // 獲取next方法得到的對象,並把上一次的數據傳遞過去 ret = gen.next(res); } catch (e) { // generator 獲取下一個yield值發生異常 return reject(e); } // 處理yield的值把它轉換成promise並執行 next(ret); return null; } /** * @param {Error} err * @return {undefined} */ function onRejected(err) { let ret; try { // 把錯誤拋到generator裏,而且接收下次的yield ret = gen.throw(err); } catch (e) { // generator 獲取下一個yield值發生異常 return reject(e); } // 處理yield的值 next(ret); } function next(ret) { // generator執行完並把返回值resolve出去 if (ret.done) { return resolve(ret.value); } // 把value轉換成Promise const value = toPromise(ctx, ret.value); if (value && isPromise(value)) { // 等待Promise執行 return value.then(onFulfilled, onRejected); } // yield的值不支持 return onRejected(new TypeError("You may only yield a function, promise," + " generator, array, or object, " + 'but the following object was passed: "' + String(ret.value) + '"')); } }); };
toPromise
function toPromise(ctx: any, obj: any) { if (!obj) { return obj; } if (isPromise(obj)) { return obj; } // 判斷是 Generator 對象|方法 直接經過 co 轉換爲Promise if (isGeneratorFunction(obj) || isGenerator(obj)) { return co.call(ctx, obj); } // 判斷是個回調方法 if ("function" === typeof obj) { return thunkToPromise(ctx, obj); } // 判斷是個數組 if (Array.isArray(obj)) { return arrayToPromise(ctx, obj); } // 根據對象屬性把全部屬性轉爲一個Promise if (isObject(obj)) { return objectToPromise(ctx, obj); } // 基礎數據類 1 , true return obj; }
轉換方法這個懶得說了
function thunkToPromise(ctx, fn) { return new Promise(function _p(resolve, reject) { fn.call(ctx, function _(err, res) { if (err) { return reject(err); } if (arguments.length > 2) { res = slice.call(arguments, 1); } resolve(res); }); }); } function arrayToPromise(ctx, obj: any[]) { return Promise.all(obj.map((item) => toPromise(ctx, item))); } function objectToPromise(ctx, obj) { const results = {}; const keys = Object.keys(obj); const promises = []; for (let i = 0, len = keys.length; i < len; i++) { const key = keys[i]; const val = obj[key]; const promise = toPromise(ctx, val); if (promise && isPromise(promise)) { promises.push(promise.then(function _(res) { results[key] = res; })); } else { results[key] = val; } } return Promise.all(promises).then(function _() { return results; }); }
還有一些判斷工具函數
function isPromise(obj: { then: Function) { return "function" === typeof obj.then; } function isGenerator(obj) { return "function" === typeof obj.next && "function" === typeof obj.throw; } function isGeneratorFunction(obj) { const constructor = obj.constructor; if (!constructor) { return false; } if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) { return true; } return isGenerator(constructor.prototype); } function isObject(val) { return Object === val.constructor; }
此次逼着本身寫3天就寫完了,果真就是懶。
接下來寫一個系列文章preact
的源碼解析與實現。
儘可能一週出一篇?看看狀況吧。