成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

JavaScript 異步編程之 jsdeferred 原理解析

LuDongWei / 1646人閱讀

摘要:異步編程是編寫(xiě)的一個(gè)很重要的理念,特別是在處理復(fù)雜應(yīng)用的時(shí)候,異步編程的技巧就至關(guān)重要。那么下面就來(lái)看看這個(gè)被稱(chēng)為里程碑式的異步編程庫(kù)吧。

1. 前言

最近在看司徒正美的《JavaScript框架設(shè)計(jì)》,看到異步編程的那一章介紹了jsdeferred這個(gè)庫(kù),覺(jué)得很有意思,花了幾天的時(shí)間研究了一下代碼,在此做一下分享。

異步編程是編寫(xiě)js的一個(gè)很重要的理念,特別是在處理復(fù)雜應(yīng)用的時(shí)候,異步編程的技巧就至關(guān)重要。那么下面就來(lái)看看這個(gè)被稱(chēng)為里程碑式的異步編程庫(kù)吧。

2. API源碼解析 2.1 構(gòu)造函數(shù)

這里使用了安全的構(gòu)造函數(shù),避免了在沒(méi)有使用new調(diào)用構(gòu)造函數(shù)時(shí)出錯(cuò)的問(wèn)題,提供了兩個(gè)形式倆獲取Deferred對(duì)象實(shí)例。

function Deferred() {
    return (this instanceof Deferred) ? this.init() : new Deferred();
}

// 方式1 
var o1 = new Deferred();
// 方式2
var o2 = Deferred();
2.2 Deferred.define()

這個(gè)方法可以包裝一個(gè)對(duì)象,指定對(duì)象的方法,或者將Deferred對(duì)象的方法直接暴露在全局作用域下,這樣就可以直接使用。

Deferred.methods = ["parallel", "wait", "next", "call", "loop", "repeat", "chain"];
/*
    @Param obj 賦予該對(duì)象Deferred的屬性方法
    @Param list 指定屬性方法
*/
Deferred.define = function(obj, list){
    if(!list)list = Deferred.methods;
    // 獲取全局作用域的技巧,利用立即執(zhí)行函數(shù)的作用域?yàn)槿肿饔糜虻募记?    if(!obj) obj = (function getGlobal(){return this})();
    // 將屬性都掛載到obj上
    for(var i = 0; i < list.length; i++){
        var n = list[i];
        obj[n] = Deferred[n];
    }
    return Deferred;
}

this.Deferred = Deferred;
2.3 異步的操作實(shí)現(xiàn)

在JSDeferred中有許多異步操作的實(shí)現(xiàn)方式,也是作為這個(gè)框架最為出彩的地方,方法依次是:

script.onreadystatechange(針對(duì)IE5.5~8)

img.onerror/img.onload(針對(duì)現(xiàn)代瀏覽器的異步操作方法)

針對(duì)node環(huán)境的,使用process.nextTick來(lái)實(shí)現(xiàn)異步調(diào)用(已經(jīng)過(guò)時(shí))

setTimeout(default)

它會(huì)視瀏覽器選擇最快的API。

使用script的onreadystatechange事件來(lái)進(jìn)行,需要注意的是由于瀏覽器對(duì)并發(fā)請(qǐng)求數(shù)有限制,(IE5.5~8為2~3,IE9+和現(xiàn)代瀏覽器為6),當(dāng)并發(fā)請(qǐng)求數(shù)大于上限時(shí),會(huì)讓請(qǐng)求的發(fā)起操作排隊(duì)執(zhí)行,導(dǎo)致延時(shí)更嚴(yán)重。代碼的思路是以150ms為一個(gè)周期,每個(gè)周期以通過(guò)setTimeout發(fā)起的異步執(zhí)行為起始,周期內(nèi)的其他異步執(zhí)行操作通過(guò)script請(qǐng)求實(shí)現(xiàn),如果此方法被頻繁調(diào)用的話(huà),說(shuō)明達(dá)到并發(fā)請(qǐng)求數(shù)上限的可能性越高,因此可以下調(diào)一下周期時(shí)間,例如設(shè)為100ms,避免因排隊(duì)導(dǎo)致的高延時(shí)。

Deferred.next_faster_way_readystatechange = ((typeof window === "object") && 
(location.protocol == "http:") && 
!window.opera &&
/MSIE/.test(navigator.userAgent)) &&
function (fun) {
var d = new Deferred();
var t = new Date().getTime();
if(t - arguments.callee._prev_timeout_called < 150){
var cancel = false; // 因?yàn)閞eadyState會(huì)一直變化,避免重復(fù)執(zhí)行
var script = document.createElement("script");
script.type = "text/javascript";
// 發(fā)送一個(gè)錯(cuò)誤的url,快速觸發(fā)回調(diào),實(shí)現(xiàn)異步操作
script.src = "data:text/javascript,";
script.onreadystatechange = function () {
    if(!cancel){
        d.canceller();
        d.call();
    }
};

d.canceller = function () {
    if(!cancel){
        cancel = true;
        script.onreadystatechange = null;
        document.body.removeChild(script);// 移除節(jié)點(diǎn)
    }
};

// 不同于img,需要添加到文檔中才會(huì)發(fā)送請(qǐng)求
document.body.appendChild(script);
} else {
// 記錄或重置起始時(shí)間
arguments.callee._prev_timeout_called = t; 
// 每個(gè)周期開(kāi)始使用setTimeout
var id = setTimeout(function (){ d.call()}, 0);
d.canceller = function () {clearTimeout(id)};
}
if(fun)d.callback.ok = fun;
return d;
}

使用img的方式,利用src屬性報(bào)錯(cuò)和綁定事件回調(diào)的方式來(lái)進(jìn)行異步操作

Deferred.next_faster_way_Image = ((typeof window === "object") &&
(typeof Image != "undefined") && 
!window.opera && document.addEventListener) && 
function (fun){
var d = new Deffered();
var img = new Image();
var hander = function () {
d.canceller();
d.call();
}
img.addEventListener("load", handler, false);
img.addEventListener("error", handler, false);

d.canceller = function (){
img.removeEventListener("load", handler, false);
img.removeEventListener("error", handler, false);
}
// 賦值一個(gè)錯(cuò)誤的URL
img.src = "data:imag/png," + Math.random();
if(fun) d.callback.ok = fun;
return d;
}

針對(duì)Node環(huán)境的,使用process.nextTick來(lái)實(shí)現(xiàn)異步調(diào)用

Deferred.next_tick = (typeof process === "object" &&
typeof process.nextTick === "function") && 
function (fun) {
var d = new Deferred();
process.nextTick(function() { d.call() });
if (fun) d.callback.ok = fun;
return d;
};

setTimeout的方式,這種方式有一個(gè)觸發(fā)最小的時(shí)間間隔,在舊的IE瀏覽器中,時(shí)間間隔可能會(huì)稍微長(zhǎng)一點(diǎn)(15ms)。

Deferred.next_default = function (fun) {
var d = new Deferred();
var id = setTimeout(function(){
clearTimeout(id);
d.call(); // 喚起Deferred調(diào)用鏈
}, 0)
d.canceller = function () {
try{
    clearTimeout(id);
}catch(e){}
};
if(fun){
d.callback.ok = fun;
}
return d;
}

默認(rèn)的順序?yàn)?/p>

Deferred.next = 
    Deferred.next_faster_way_readystatechange || // 處理IE
    Deferred.next_faster_way_Image || // 現(xiàn)代瀏覽器
    Deferred.next_tick || // node環(huán)境
    Deferred.next_default; // 默認(rèn)行為

根據(jù)JSDeferred官方的數(shù)據(jù),使用next_faster_way_readystatechangenext_faster_way_Image這兩個(gè)比原有的setTimeout異步的方式快上700%以上。

看了一下數(shù)據(jù),其實(shí)對(duì)比的瀏覽器版本都相對(duì)比較舊,在現(xiàn)代的瀏覽器中性能提升應(yīng)該就沒(méi)有那么明顯了。

2.4 原型方法

Deferred的原型方法中實(shí)現(xiàn)了

_id 用來(lái)判斷是否是Deferred的實(shí)例,原因好像是Mozilla有個(gè)插件也叫Deferred,因此不能通過(guò)instanceof來(lái)檢測(cè)。cho45于是自定義標(biāo)志位來(lái)作檢測(cè),并在github上提交fxxking Mozilla。

init 初始化,給每個(gè)實(shí)例附加一個(gè)_nextcallback屬性

next 用于注冊(cè)調(diào)用函數(shù),內(nèi)部以鏈表的方式實(shí)現(xiàn),節(jié)點(diǎn)為Deferred實(shí)例,調(diào)用的內(nèi)部方法_post

error 用于注冊(cè)函數(shù)調(diào)用失敗時(shí)的錯(cuò)誤信息,與next的內(nèi)部實(shí)現(xiàn)一致。

call 喚起next調(diào)用鏈

fail 喚起error調(diào)用鏈

cancel 執(zhí)行cancel回調(diào),只有在喚起調(diào)用鏈之前調(diào)用才有效。(調(diào)用鏈?zhǔn)菃蜗虻?,?zhí)行之后就不可返回)

Deferred.prototype = {
    _id : 0xe38286e381ae, // 用于判斷是否是實(shí)例的標(biāo)識(shí)位
    init : function () {
        this._next = null; // 一種鏈表的實(shí)現(xiàn)思路
        this.callback = {
            ok : Deferred.ok, // 默認(rèn)的ok回調(diào)
            ng : Deferred.ng  // 出錯(cuò)時(shí)的回調(diào)
        };
        return this;
    },
    next : function (fun) {
        return this._post("ok", fun); // 調(diào)用_post建立鏈表
    },
    error : function (fun) {
        return this._post("ng", fun); // 調(diào)用_post建立鏈表
    },
    call : function(val) {
        return this._fire("ok", val); // 喚起next調(diào)用鏈
    },
    fail : function (err) {
        return this._fire("ng", err); // 喚起error調(diào)用鏈
    },
    cancel : function () {
        (this.canceller || function () {}).apply(this);
        return this.init(); // 進(jìn)行重置
    },
    _post : function (okng, fun){ // 建立鏈表
        this._next = new Deferred();
        this._next.callback[okng] = fun;
        return this._next;
    },
    _fire : function (okng, fun){
        var next = "ok";
        try{
            // 注冊(cè)的回調(diào)函數(shù)中,可能會(huì)拋出異常,用try-catch進(jìn)行捕捉
            value = this.callback[okng].call(this, value); 
        } catch(e) {
            next = "ng";
            value = e; // 傳遞出錯(cuò)信息
            if (Deferred.onerror) Deferred.onerror(e); // 發(fā)生錯(cuò)誤的回調(diào)
        }
        if (Deferred.isDeferred(value)) { // 判斷是否是Deferred的實(shí)例
            // 這里的代碼就是給Deferred.wait方法使用的,
            value._next = this._next;
        } else { // 如果不是,則繼續(xù)執(zhí)行
            if (this._next) this._next._fire(next, value);
        }
        return this;
    }
}
2.5 輔助靜態(tài)方法

上面的代碼中,可以看到一些Deferred對(duì)象的方法(靜態(tài)方法),下面簡(jiǎn)單介紹一下:

// 默認(rèn)的成功回調(diào)
Deferred.ok = function (x) {return x};

// 默認(rèn)的失敗回調(diào)
Deferred.ng = function (x) {throw x};

// 根據(jù)_id判斷實(shí)例的實(shí)現(xiàn)
Deferred.isDeferred = function (obj) {
    return !!(obj && obj._id === Deferred.prototype._id);
}
2.6 簡(jiǎn)單小結(jié)

看到這里,我們需要停下來(lái),看看一個(gè)簡(jiǎn)單的例子,來(lái)理解整個(gè)流程。

Defferred對(duì)象自身有next屬性方法,在原型上也定義了next方法,需要注意這一點(diǎn),例如以下代碼:

var o = {};
Deferred.define(o);
o.next(function fn1(){
    console.log(1);
}).next(function fn2(){
    console.log(2);
});

o.next()是Deffered對(duì)象的屬性方法,這個(gè)方法會(huì)返回一個(gè)Defferred對(duì)象的實(shí)例,因此下一個(gè)next()則是原型上的next方法。

第一個(gè)next()方法將后續(xù)的代碼變成異步操作,后面的next()方法實(shí)際上是注冊(cè)調(diào)用函數(shù)。

在第一個(gè)next()的異步操作里面喚起后面next()的調(diào)用鏈(d.call()),開(kāi)始順序的調(diào)用,換句話(huà)說(shuō)就是,fn1和fn2是同步執(zhí)行的。

那么,如果我們希望fn1和fn2也是異步執(zhí)行,而不是同步執(zhí)行的,這就得借助Deferred.wait方法了。

2.7 wait & register

我們可以使用wait來(lái)讓fn1和fn2變成異步執(zhí)行,代碼如下:

Deferred.next(function fn1() {
    console.log(1)
}).wait(0).next(function fn2() {
    console.log(2)
});

wait方法很有意思,在Deferred的原型上并沒(méi)有wait方法,而是在靜態(tài)方法上找到了。

Deferred.wait = function (n) {
    var d = new Deferred(),
        t = new Date();
    // 使用定時(shí)器來(lái)變成異步操作
    var id = setTimeout(function () {
        d.call((new Date()).getTime() - t.getTime());
    }, n * 1000);

    d.canceller = function () {
        clearTimeout(id);
    }
    return d;
}

那么這個(gè)方法是怎么放到原型上的?原來(lái)是通過(guò)Deferred.register進(jìn)行函數(shù)轉(zhuǎn)換,綁定到原型上的。

Deferred.register = function (name, fun){
    this.prototype[name] = function () { // 柯里化
        var a = arguments;
        return this.next(function(){
            return fun.apply(this, a);
        });
    }
};

// 將方法注冊(cè)到原型上
Deferred.register("wait", Deferred.wait);

我們需要思考為什么要用這種方式將wait方法register到Deferred的原型對(duì)象上去?,因?yàn)槊黠@這種方式有點(diǎn)難以理解。

結(jié)合例子,我們進(jìn)行討論,便能夠徹底地理解上述的問(wèn)題。

Deferred.next(function fn1(){ // d1
    console.log(1);
})
.wait(1) // d2
.next(function fn2(){ // d3
    console.log(2);
});

這段代碼首先會(huì)建立一個(gè)調(diào)用鏈

之后,執(zhí)行的過(guò)程為(如圖所示)

我們來(lái)看看執(zhí)行過(guò)程的幾個(gè)關(guān)鍵點(diǎn)

圖中的d1、d2、d3、d_wait表示在調(diào)用鏈上生成的Deferred對(duì)象的實(shí)例

在調(diào)用了d2的callback.ok即包裝了wait()方法的匿名函數(shù)之后,返回了在wait()方法中生成的Deferred對(duì)象的實(shí)例d_wait,保存在變量value中,在_fire()方法中有一個(gè)if判斷

if(Deferred.isDeferred(value)){
    value._next = this._next;
}

在這里并沒(méi)有繼續(xù)往下執(zhí)行調(diào)用鏈的函數(shù),而是重新建立了一個(gè)調(diào)用鏈,此時(shí)鏈頭為d_wait,在wait()方法中使用setTimeout,使其異步執(zhí)行,使用d.call()重新喚起調(diào)用鏈。

理解了整個(gè)過(guò)程,就比較好回到上面的問(wèn)題了。之所以使用register的方式是因?yàn)樵蜕系膚ait方法并非直接使用Deferred.wait,而是把Deferred.wait方法作為參數(shù),對(duì)原型上的next()方法進(jìn)行curry化,然后返回一個(gè)柯里化之后的next()方法。而Deferred.wait()其實(shí)和Deferred.next()的作用很類(lèi)似,都是異步執(zhí)行接下來(lái)的操作。

2.8 并歸結(jié)果 parallel

設(shè)想一個(gè)場(chǎng)景,我們需要多個(gè)異步網(wǎng)絡(luò)查詢(xún)?nèi)蝿?wù),這些任務(wù)沒(méi)有依賴(lài)關(guān)系,不需要區(qū)分前后,但是需要等待所有查詢(xún)結(jié)果回來(lái)之后才能進(jìn)一步處理,那么你會(huì)怎么做?在比較復(fù)雜的應(yīng)用中,這個(gè)場(chǎng)景經(jīng)常會(huì)出現(xiàn),如果我們采用以下的方式(見(jiàn)偽代碼)

var result = [];
$.ajax("task1", function(ret1){
    result.push(ret1);
    $.ajax("task2", function(ret2){
        result.push(ret2);
        // 進(jìn)行操作
    });
});

這種方式可以,但是卻無(wú)法同時(shí)發(fā)送task1task2(從代碼上看還以為之間有依賴(lài)關(guān)系,實(shí)際上沒(méi)有)。那怎么解決?這就是Deferred.parallel()所要解決的問(wèn)題。

我們先來(lái)個(gè)簡(jiǎn)單的例子感受一下這種并歸結(jié)果的方式。

Deferred.parallel(function () {
    return 1;
}, function () {
    return 2;
}, function () {
    return 3;
}).next(function (a) {
    console.log(a); // [1,2,3]
});

在parallel()方法執(zhí)行之后,會(huì)將結(jié)果合并為一個(gè)數(shù)組,然后傳遞給next()中的callback.ok中??梢钥吹絧arallel里面都是同步的方法,先來(lái)看看parallel的源碼是如何實(shí)現(xiàn),再來(lái)看看能不能結(jié)合所學(xué)來(lái)改造實(shí)現(xiàn)我們所需要的ajax的效果。

Deferred.parallel = function (dl) {
    /* 
        前面都是對(duì)參數(shù)的處理,可以接收三種形式的參數(shù) 
        1. parallel(fn1, fn2, fn3).next()
        2. parallel({
                foo : $.get("foo.html"),
                bar : $.get("bar.html")
            }).next(function (v){
                v.foo // => foo.html data
                v.bar // => bar.html data
            });
        3. parallel([fn1, fn2, fn3]).next(function (v) {
                v[0] // fn1執(zhí)行的結(jié)果
                v[1] // fn2執(zhí)行的結(jié)果
                v[3] // fn3執(zhí)行返回的結(jié)果
            });
    */
    var isArray = false;
    // 第一種形式
    if (arguments.length > 1) {
        dl = Array.prototype.slice.call(arguments);
        isArray = true;
    // 其余兩種形式,數(shù)組,類(lèi)數(shù)組
    } else if (Array.isArray && Array.isArray(dl) 
                || typeof dl.length == "number") {
        isArray = true;
    }
    var ret = new Deferred(), // 用于歸并結(jié)果的Deferred對(duì)象的實(shí)例
        value = {}, // 收集函數(shù)執(zhí)行的結(jié)果
        num = 0 ; // 計(jì)數(shù)器,當(dāng)為0時(shí)說(shuō)明所有任務(wù)都執(zhí)行完畢
    
    // 開(kāi)始遍歷,這里使用for-in其實(shí)效率不高
    for (var i in dl) {
        // 預(yù)防遍歷了所有屬性,例如toString之類(lèi)的
        if (dl.hasOwnProperty(i)) {
            // 利用閉包保存變量狀態(tài)
            (function (d, i){
                // 使用Deferred.next()開(kāi)始一個(gè)異步任務(wù),并且執(zhí)行完成之后,收集結(jié)果
                if (typeof d == "function") dl[i] = d = Deferred.next(d);
                d.next(function (v) {
                    values[i] = v;
                    if( --num <= 0){ // 計(jì)數(shù)器為0說(shuō)明所有任務(wù)已經(jīng)完成,可以返回
                        if(isArray){ // 如果是數(shù)組的話(huà),結(jié)果可以轉(zhuǎn)換成數(shù)組
                            values.length = dl.length;
                            values = Array.prototype.slice.call(values, 0);
                        }
                        // 調(diào)用parallel().next(function(v){}),喚起調(diào)用鏈
                        ret.call(values);
                    }
                }).error(function (e) {
                    ret.fail(e);
                });
                num++; // 計(jì)數(shù)器加1
            })(d[i], i);
        } 
    }
    
    // 當(dāng)計(jì)算器為0的時(shí)候,處理可能沒(méi)有參數(shù)或者非法參數(shù)的情況
    if (!num) {
        Deferred.next(function () { 
            ret.call();
        });
    } 

    ret.canceller = function () {
        for (var i in dl) {
            if (dl.hasOwnProperty(i)) {
                dl[i].cancel();
            }
        }
    };
    return ret; // 返回Deferred實(shí)例
};

結(jié)合上述知識(shí),我們可以在parallel中使用異步方法,代碼如下

Deferred.parallel(function fn1(){
    var d = new Deferred();
    $.ajax("task1", function(ret1){
        d.call(ret1);
    });
    return d;
}, function () {
    var d = new Deferred();
    $.ajax("task2", function fn2(ret2) {
        d.call(ret2)
    });
    return d;
}).next(function fn3(ret) {
    ret[0]; // => task1返回的結(jié)果
    ret[1]; // => task2返回的結(jié)果
});

為什么可以這樣?我們來(lái)圖解一下,加深一下理解。

我們使用了_fire中的if判斷,建立了新的調(diào)用鏈,獲得去統(tǒng)計(jì)計(jì)數(shù)函數(shù)(即parallel中--num)的控制權(quán),從而使得在parallel執(zhí)行異步的方法。

問(wèn)題解決!

考慮到篇幅問(wèn)題,其他的源碼分析放在了我自己的gitbook上,歡迎交流探討。

參考資料

jsdeferred.js

jsDeferred API

JavaScript框架設(shè)計(jì)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/91006.html

相關(guān)文章

  • 50道JavaScript基礎(chǔ)面試題(附答案)

    摘要:事件中屬性等于。響應(yīng)的狀態(tài)為或者。同步在上會(huì)產(chǎn)生頁(yè)面假死的問(wèn)題。表示聲明的變量未初始化,轉(zhuǎn)換為數(shù)值時(shí)為。但并非所有瀏覽器都支持事件捕獲。它由兩部分構(gòu)成函數(shù),以及創(chuàng)建該函數(shù)的環(huán)境。 1 介紹JavaScript的基本數(shù)據(jù)類(lèi)型Number、String 、Boolean 、Null、Undefined Object 是 JavaScript 中所有對(duì)象的父對(duì)象數(shù)據(jù)封裝類(lèi)對(duì)象:Object、...

    huaixiaoz 評(píng)論0 收藏0
  • ES6 Features系列:GeneratorFunction介紹

    摘要:沒(méi)有顯示顯示顯示關(guān)鍵字迭代器生成器用于馬上退出代碼塊并保留現(xiàn)場(chǎng),當(dāng)執(zhí)行迭代器的函數(shù)時(shí),則能從退出點(diǎn)恢復(fù)現(xiàn)場(chǎng)并繼續(xù)執(zhí)行下去。迭代器迭代器是一個(gè)擁有方法和方法的對(duì)象,通過(guò)函數(shù)不斷執(zhí)行以關(guān)鍵字分割的代碼段,通過(guò)函數(shù)令分割的代碼段拋出異常。 一、前言                            第一次看koajs的示例時(shí),發(fā)現(xiàn)該語(yǔ)句 function *(next){..........

    golden_hamster 評(píng)論0 收藏0
  • JavaScript 工作原理四-事件循環(huán)及異步編程的出現(xiàn)和 5 種更好的 async/await

    摘要:函數(shù)會(huì)在之后的某個(gè)時(shí)刻觸發(fā)事件定時(shí)器。事件循環(huán)中的這樣一次遍歷被稱(chēng)為一個(gè)。執(zhí)行完畢并出棧。當(dāng)定時(shí)器過(guò)期,宿主環(huán)境會(huì)把回調(diào)函數(shù)添加至事件循環(huán)隊(duì)列中,然后,在未來(lái)的某個(gè)取出并執(zhí)行該事件。 原文請(qǐng)查閱這里,略有改動(dòng)。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原理的第四章。 現(xiàn)在,我們將會(huì)通過(guò)回顧單線(xiàn)程環(huán)境下編程的弊端及如何克服這些困難以創(chuàng)建令人驚嘆...

    maochunguang 評(píng)論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護(hù)很多人說(shuō),阮老師已經(jīng)有一本關(guān)于的書(shū)了入門(mén),覺(jué)得看看這本書(shū)就足夠了。前端的異步解決方案之和異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(shū)(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書(shū)的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<