摘要:簡單實(shí)現(xiàn)前言你可能知道,的任務(wù)執(zhí)行的模式有兩種同步和異步。你已經(jīng)實(shí)現(xiàn)了方法方法是一個(gè)很好用的方法。感興趣的朋友可以自行去研究哈附上代碼完整的實(shí)現(xiàn)個(gè)人博客鏈接
Promise 簡單實(shí)現(xiàn) 前言
你可能知道,javascript 的任務(wù)執(zhí)行的模式有兩種:同步和異步。
異步模式非常重要,在瀏覽器端,耗時(shí)很長的操作(例如 ajax 請求)都應(yīng)該異步執(zhí)行,避免瀏覽器失去響應(yīng)。
在異步模式編程中,我們經(jīng)常使用回調(diào)函數(shù)。一不小心就可能寫出以下這樣的代碼:
//事件1 doSomeThing1(function() { //事件2 doSomeThing2(function() { //事件3 doSomeThing3(); }); });
當(dāng)你的需要異步執(zhí)行的函數(shù)越來越多,你的層級也會(huì)越來越深。
這樣的寫法存在的缺點(diǎn)是:
不利于閱讀
各個(gè)任務(wù)之間的高度耦合,難以維護(hù)
對異常的處理比較難
用 Promise 可以避免這種回調(diào)地獄,可以寫成這樣
//事件1 doSomeThing1() .then(function() { //事件2 return doSomeThing2(); }) .then(function() { //事件3 return doSomeThing3(); }) .catch(function() { //這里可以很方便的做異常處理 });
在市面上有許多庫都實(shí)現(xiàn)了 Promise,例如:Q.js 、when.js ,es6 也將 Promise 納入了標(biāo)準(zhǔn)中
es6 的 Promise 使用方法可以參考阮一峰的 http://es6.ruanyifeng.com/#do... ,我就不在做具體介紹
接下來,我們模仿 ES6 的 promise,一步一步來實(shí)現(xiàn)一個(gè)簡單版的 Promise。
構(gòu)造函數(shù)我們使用 Promise 的時(shí)候,
const promise = new Promise((resolve, reject)=>{ // ... some code if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } });
Promise 是一個(gè)構(gòu)造函數(shù),接收一個(gè)函數(shù),函數(shù)里有兩個(gè)參數(shù),resolve、reject。
我們可以這樣子實(shí)現(xiàn):
class PromiseA { constructor(executor) { const resolve = value => { this.resolve(value); }; const reject = err => { this.reject(err); }; try { executor(resolve, reject); } catch (err) { reject(err); } } resolve(value) { this.resultValue = value; } reject(error) { this.resultValue = error; } }then 方法
promise 中,用的最頻繁的就是 then 方法,then 方法有兩個(gè)參數(shù),一個(gè)是 promise 成功時(shí)候的回調(diào),一個(gè)是失敗的回調(diào)。
實(shí)現(xiàn)方式為:
class PromiseA { constructor(executor) { const resolve = value => { this.resolve(value); }; const reject = err => { this.reject(err); }; try { executor(resolve, reject); } catch (err) { reject(err); } } resolve(value) { this.resultValue = value; if (this.fullfillCallback) { //++++++++ this.fullfillCallback(value); } } reject(error) { this.resultValue = error; if (this.rejectCallback) { //++++++++ this.rejectCallback(value); } } then(resolve, reject) { this.fullfillCallback = resolve; this.rejectCallback = resolve; } }then 方法有以下幾個(gè)特性:
支持鏈?zhǔn)讲僮?/p>
每次 then 方法都是返回新的 Promise
當(dāng)前 promise 的狀態(tài)通過返回值傳遞給下一個(gè) promise
錯(cuò)誤冒泡,即如果當(dāng)前 promise 沒有提供 onReject 方法,會(huì)把錯(cuò)誤冒泡到下一個(gè) promise,方便處理
then(onResolve,onReject){ //返回新的Promise并支持鏈?zhǔn)讲僮? return new PromiseA((resolve,reject)=>{ this.fullfillCallback = (value)=>{ try { if (onResolve) { let newValue = onResolve(value); resolve(newValue); //將當(dāng)前promise執(zhí)行結(jié)果,傳遞給下一個(gè)promise } else { resolve(value); } } catch (err) { reject(err); } } //類似fullfillCallback this.rejectCallback = (value)=>{ try { if (onReject) { let newValue = onReject(value); resolve(newValue); } else { //錯(cuò)誤冒泡 reject(value); } } catch (err) { reject(err); } } }); }
這樣我們就實(shí)現(xiàn)了一個(gè)簡單版的 then 方法了
加強(qiáng)版 then上面的實(shí)現(xiàn),模擬了 then 方法的邏輯,但是還有一些缺點(diǎn):
then 方法只能添加一個(gè),例如
let p = new PromiseA(resolve => { setTimeout(() => { resolve(1); }, 0); }); p.then(value => { console.log("then-->" + value); }); //無輸出,因?yàn)闆]觸發(fā)到,被后一個(gè)覆蓋了 p.then(value => { console.log("then2-->" + value); }); ////then---> 1
promise 沒有狀態(tài),當(dāng) promsie 在添加 then 的時(shí)候已經(jīng)完成了,沒法得到結(jié)果
沒有實(shí)現(xiàn):如果上一個(gè) promise 的返回值也是一個(gè) Promise 對象時(shí),則會(huì)等到這個(gè) Promise resolve 的時(shí)候才執(zhí)行下一個(gè)
為了解決第一點(diǎn),引入了事件監(jiān)聽,簡單的實(shí)現(xiàn)如下:export default class EventEmitter { constructor() { this._events = {}; } on(type, fn, context = this) { if (!this._events[type]) { this._events[type] = []; } this._events[type].push([fn, context]); } trigger(type) { let events = this._events[type]; if (!events) { return; } let len = events.length; let eventsCopy = [...events]; for (let i = 0; i < len; i++) { let event = eventsCopy[i]; let [fn, context] = event; if (fn) { fn.apply(context, [].slice.call(arguments, 1)); } } } }
所以進(jìn)一步對 PromiseA 進(jìn)行改造:
const STATUS = { PENDING: "pending", FULFILLED: "fulfilled", REJECTED: "rejected" }; const EventType = { fulfill: "fulfill", reject: "reject" }; class PromiseA { constructor(executor) { //初始化事件監(jiān)聽及狀態(tài) this.eventEmitter = new EventEmitter(); this.status = STATUS.PENDING; const resolve = value => { if (value instanceof PromiseA) { value.then( value => { this.resolve(value); }, error => { this.reject(error); } ); } else { this.resolve(value); } }; const reject = err => { this.reject(err); }; try { executor(resolve, reject); } catch (err) { reject(err); } } resolve(value) { //增加狀態(tài) if (this.status === STATUS.PENDING) { this.status = STATUS.FULFILLED; this.resultValue = value; this.eventEmitter.trigger(EventType.fulfill, value); } } reject(error) { //增加狀態(tài) if (this.status === STATUS.PENDING) { this.status = STATUS.REJECTED; this.resultValue = error; this.eventEmitter.trigger(EventType.reject, error); } } then(onResolve, onReject) { //根據(jù)狀態(tài)不同處理 if (this.status === STATUS.PENDING) { return new PromiseA((resolve, reject) => { //增加事件監(jiān)聽 this.eventEmitter.on("fulfill", value => { try { if (onResolve) { let newValue = onResolve(value); resolve(newValue); } else { resolve(value); } } catch (err) { reject(err); } }); //增加事件監(jiān)聽 this.eventEmitter.on("reject", value => { try { if (onReject) { let newValue = onReject(value); resolve(newValue); } else { reject(value); } } catch (err) { reject(err); } }); }); } if ( this.status === STATUS.FULFILLED || this.status === STATUS.REJECTED ) { return new PromiseA((resolve, reject) => { let callback = returnValue; if (this.status === STATUS.FULFILLED) { callback = onResolve; } if (this.status === STATUS.REJECTED) { callback = onReject; } try { let newValue = callback(this.resultValue); resolveValue(newValue, resolve, reject); } catch (err) { reject(err); } }); } } }
到這里,我們的 then 方法基本就完成了。
最后還有一個(gè)小知識(shí)點(diǎn),就是執(zhí)行時(shí)機(jī)的問題:setTimeout(function() { console.log(4); }, 0); new Promise(function(resolve) { console.log(1); resolve(); }).then(function() { console.log(3); }); console.log(2); //輸出結(jié)果會(huì)是: 1、2、3、4
promise.then,是異步的,屬于 microtask,執(zhí)行時(shí)機(jī)是本次事件循環(huán)結(jié)束之前,而 setTimeout 是 macrotask,執(zhí)行時(shí)機(jī)是在下一次事件循環(huán)的開始之時(shí)
實(shí)現(xiàn)這個(gè)功能,我利用了第三方庫 microtask 來模擬。所以 PromiseA 修改為:
resolve(value) { microtask(() => { if (this.status === STATUS.PENDING) { this.status = STATUS.FULFILLED; this.resultValue = value; this.eventEmitter.trigger(EventType.fulfill, value); } }) } reject(error) { microtask(() => { if (this.status === STATUS.PENDING) { this.status = STATUS.REJECTED; this.resultValue = error; this.eventEmitter.trigger(EventType.reject, error); } }); }
到此為止,我們的 then 方法已經(jīng)大功告成了。最困難的一步已經(jīng)解決了
catchPromise 跟普通回調(diào)的一大優(yōu)勢就是異常處理,我們推薦使用 Promise 的時(shí)候,總是使用 catch 來代替 then 的第二個(gè)參數(shù)。即是:
//bad let p = new Promise((resolve, reject) => { //...異步操作 }).then( value => { //成功 }, () => { //失敗 } ); //good let p = new Promise((resolve, reject) => { //...異步操作 }) .then(value => { //成功 }) .catch(() => { //失敗 });
接下來讓我們實(shí)現(xiàn) catch 方法:
catch(reject) { return this.then(null, reject); }
哈哈~ , 你沒看錯(cuò)。你已經(jīng)實(shí)現(xiàn)了 catch 方法
all 方法Promise.all 是一個(gè)很好用的方法。接受一個(gè) promise 數(shù)組,然后等到所有的異步操作都完成了,就返回一個(gè)數(shù)組,包含對應(yīng)的值
具體實(shí)現(xiàn)如下:
static all(promiseList = []) { //返回promise以便鏈?zhǔn)讲僮? return new PromiseA((resolve, reject) => { let results = []; let len = promiseList.length; let resolveCount = 0; //用于計(jì)數(shù) let resolver = function (index, value) { resolveCount++; results[index] = value; if (resolveCount === len) { resolve(results); } }; //遍歷執(zhí)行所有的promise promiseList.forEach((p, i) => { if (p instanceof PromiseA) { p.then((value) => { resolver(i, value); }, (err) => { reject(err); }) } else { resolver(i, p); } }) }); }race 方法
race 方法為競速,第一執(zhí)行完的為準(zhǔn)。所以只需循環(huán)一遍執(zhí)行就可以了。
當(dāng)有第一個(gè)將 Promise 的狀態(tài)改變成 fullfilled 或 reject 之后,其他的就都無效了
static race(promiseList = []) { return new PromiseA((resolve, reject) => { promiseList.forEach((p, i) => { if (p instanceof PromiseA) { p.then((value) => { resolve(value); }, (err) => { reject(err); }) } else { resolve(p); } }) }) }小結(jié)
我們實(shí)現(xiàn)了一個(gè)簡單版的 promise, 還有一些其他的方法在這里沒有講到。感興趣的朋友可以自行去研究哈~
附上代碼完整的實(shí)現(xiàn) : https://github.com/chen434202...
個(gè)人博客鏈接:https://chen4342024.github.io...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/100233.html
摘要:近幾年隨著開發(fā)模式的逐漸成熟,規(guī)范順勢而生,其中就包括提出了規(guī)范,完全改變了異步編程的寫法,讓異步編程變得十分的易于理解。最后,是如此的優(yōu)雅但也只是解決了回調(diào)的深層嵌套的問題,真正簡化異步編程的還是,在端,建議考慮。 本篇,簡單實(shí)現(xiàn)一個(gè)promise,主要普及promise的用法。 一直以來,JavaScript處理異步都是以callback的方式,在前端開發(fā)領(lǐng)域callback機(jī)制...
摘要:實(shí)現(xiàn)的一個(gè)簡單的如果有錯(cuò)誤的地方,希望大家能夠不吝賜教僅實(shí)現(xiàn)及方法最下方有完整代碼開始一個(gè)對象接收的是一個(gè)這個(gè)接收兩個(gè)參數(shù)當(dāng)我們在內(nèi)執(zhí)行或的時(shí)候,就會(huì)調(diào)用內(nèi)定義的和函數(shù)然后,和函數(shù)會(huì)改變的狀態(tài)所以它應(yīng)該是像下面這樣的保存值記錄狀態(tài)為,為,為 實(shí)現(xiàn)的一個(gè)簡單的ES6 Promise(如果有錯(cuò)誤的地方,希望大家能夠不吝賜教) 僅實(shí)現(xiàn)Promise及.then方法最下方有完整代碼 開始 一個(gè)...
摘要:在和方法執(zhí)行的時(shí)候訂閱事件,將自己的回調(diào)函數(shù)綁定到事件上,屬性是發(fā)布者,一旦它的值發(fā)生改變就發(fā)布事件,執(zhí)行回調(diào)函數(shù)。實(shí)現(xiàn)和方法的回調(diào)函數(shù)都是,當(dāng)滿足條件對象狀態(tài)改變時(shí),這些回調(diào)會(huì)被放入隊(duì)列。所以我需要在某個(gè)變?yōu)闀r(shí),刪除它們綁定的回調(diào)函數(shù)。 前言 按照文檔說明簡單地實(shí)現(xiàn) ES6 Promise的各個(gè)方法并不難,但是Promise的一些特殊需求實(shí)現(xiàn)起來并不簡單,我首先提出一些不好實(shí)現(xiàn)或者容...
摘要:為了降低異步編程的復(fù)雜性,所以。難理解請參考的誤區(qū)以及實(shí)踐異步編程的模式異步編程的種方法 異步編程 javascript異步編程, web2.0時(shí)代比較熱門的編程方式,我們平時(shí)碼的時(shí)候也或多或少用到,最典型的就是異步ajax,發(fā)送異步請求,綁定回調(diào)函數(shù),請求響應(yīng)之后調(diào)用指定的回調(diào)函數(shù),沒有阻塞其他代碼的執(zhí)行。還有像setTimeout方法同樣也是異步執(zhí)行回調(diào)的方法。 如果對異步編程...
摘要:近幾年隨著開發(fā)模式的逐漸成熟,規(guī)范順勢而生,其中就包括提出了規(guī)范,完全改變了異步編程的寫法,讓異步編程變得十分的易于理解。最后,是如此的優(yōu)雅但也只是解決了回調(diào)的深層嵌套的問題,真正簡化異步編程的還是,在端,建議考慮。 前段時(shí)間頻頻看到Promise這個(gè)詞,今天發(fā)現(xiàn)騰訊AlloyTeam寫得這篇很贊,遂轉(zhuǎn)之。 原文鏈接 本篇,主要普及promise的用法。 一直以來,JavaScrip...
閱讀 1937·2021-11-11 16:55
閱讀 808·2019-08-30 15:53
閱讀 3666·2019-08-30 15:45
閱讀 796·2019-08-30 14:10
閱讀 3325·2019-08-30 12:46
閱讀 2182·2019-08-29 13:15
閱讀 2083·2019-08-26 13:48
閱讀 988·2019-08-26 12:23