Promise延伸啓示錄

promise

每一個異步請求立刻返回一個Promise對象,由於是立刻返回,所以可以採用同步操作的流程。而Promise的then方法,允許指定回調函數,在異步任務完成後調用
下面的setTimeout()可以代替理解爲一個ajax請求,所以ajax請求同理

function a() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('執行任務a');
            resolve('執行任務a成功');
        }, 1000);
    });
}

function b(value) {
    console.log(value)
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('執行任務b');
            resolve('執行任務b成功');
        }, 2000);
    });
}

function c() {
    console.log('最後執行c')
}
a().then(b).then(c);
  • 如果then裏return的值是promise則將resolve的結果傳入下一個then
  • 如果then裏return返回的不是promise則將結果直接傳入下一個then

類promise

很多像promise的異步封裝方法,比如angular1.x內置封裝的$http方法,如下,可以實現多個回調的鏈式調用,避免了金字塔式的回調地獄

//1
$http({
	method: 'GET',
	url: 'news.json',
}).then(function successCallback(response) {
	console.log(response)
}, function errorCallback(response) {
	console.log(response)
})
//2
.then(function() {
	return $http({
		method: 'GET',
		url: 'data.json',
	})
}).then(function(data) {
	console.log(data)
})
//3
.then(function() {
	setTimeout(function() {
		console.log("定時器")
	}, 1000)
})

await

await 是個運算符,用於組成表達式,await 表達式的運算結果取決於它等的東西。

如果它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。

如果它等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等着 Promise 對象 resolve,然後得到 resolve 的值,作爲 await 表達式的運算結果。

function a() {
  return new Promise(function(resolve){
    setTimeout(()=>{
    	console.log("a")
    	resolve()
    },1000)
  });
}

function b() {
  return new Promise(function(resolve){
    setTimeout(()=>{
    	console.log("b")
    	resolve()
    },1000)
  });
}

function c() {
  return new Promise(function(resolve){
    setTimeout(()=>{
    	console.log("c")
    	resolve()
    },1000)
  });
}

//ES6
a()
  .then(b)
  .then(c);

//ES2017
await a();
await b();
await c();

await等待的雖然是promise對象,但不必寫.then(..),直接可以得到返回值,所以使用await就沒有了多個then的鏈式調用

var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve();
        }, time);
    })
};

var start = async function () {
    // 在這裏使用起來就像同步代碼那樣直觀
    console.log('start');
    await sleep(3000);
    console.log('end');
};
start();
  • async表示這是一個async函數,await只能用在這個函數裏面。
  • await表示在這裏等待promise返回結果了,再繼續執行。
  • await後面跟着的應該是一個promise對象(當然,其他返回值也沒關係,不過那樣就沒有意義了)

deferred

$.ajax()操作完成後,如果使用的是低於1.5.0版本的jQuery,返回的是XHR對象,你沒法進行鏈式操作;如果高於1.5.0版本,返回的是deferred對象,可以進行鏈式操作,done()相當於success方法,fail()相當於error方法。採用鏈式寫法以後,代碼的可讀性大大提高,deferred對象的一大好處,就是它允許你自由添加多個回調函數

$.when(function(dtd) {
	var dtd = $.Deferred(); // 新建一個deferred對象
	setTimeout(function() {
		console.log(0);
		dtd.resolve(1); // 改變deferred對象的執行狀態 觸發done回調
		//dtd.reject(); //跟resolve相反,觸發fail回調
	}, 1000);
	return dtd;
}()).done(function(num) {
	console.log(num);
}).done(function() {
	console.log(2);
}).done(function() {
	console.log(2);
})

//ajax默認就是返回deferred對象
$.when($.post("index.php", {
		name: "wscat",
	}), $.post("other.php"))
	.done(function(data1, data2) {
		//兩個ajax成功纔可以進入done回調
		console.log(data1, data2);
	}).fail(function(err) {
		//其中一個ajax失敗都會進入fail回調
		console.log(err)
	})

event loop

先處理微任務隊列再處理宏任務隊列

微任務 宏任務
then setTimeout
console.log('script start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);//定時器爲宏任務
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {//then爲微任務
  console.log('promise2');
});
console.log('script end');
//先同步後異步
//先清空微任務再清空宏任務

輸出的順序是:script start, script end, promise1, promise2, setTimeout

console.log('script start');
setTimeout(function() {
  console.log('timeout1');
}, 10);
new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})
console.log('script end');

輸出的順序是:script start, promise1, script end, then1, timeout1, timeout2

配置await/async環境

安裝一下依賴

npm i -D babel-core babel-polyfill babel-preset-es2015 babel-preset-stage-0 babel-loader

新建.babelrc文件,輸入以下內容

{
    "presets": [
        "stage-0",
        "es2015"
    ]
}

新建一份index.js,把你的邏輯文件app.js,後面require的任何模塊都交給babel處理,polyfill支持awaitasync

require("babel-core/register");
require("babel-polyfill");
require("./app.js");

參考Babel 6 regeneratorRuntime is not defined

await,async,promise三者配合

//async定義裏面的異步函數順序執行
((async() => {
	try {
		//await相當於等待每個異步函數執行完成,然後繼續下一個await函數
		const a = await (() => {
			return new Promise((resolve, reject) => {
				setTimeout(() => {
					console.log(1)
					resolve(2);
					//reject(3)
				}, 1000)
			});
		})();
		const b = await (() => {
			return new Promise((resolve, reject) => {
				setTimeout(() => {
					console.log(1)
					//resolve(2);
					reject(3)
				}, 1000)
			});
		})();
		console.log(4)
		console.log(a) //3
		return b;
	} catch(err) {
		//上面try中一旦觸發reject則進入這個分支
		console.log(err);
		return err;
	}
})()).then((data) => {
	console.error(data)
}).catch((err) => {
	console.error(err)
})
//分別輸出213

注意點:

  • async用來申明裏麪包裹的內容可以進行同步的方式執行,await則是進行執行順序控制,每次執行一個await,程序都會暫停等待await返回值,然後再執行之後的await。
  • await後面調用的函數需要返回一個promise,另外這個函數是一個普通的函數即可,而不是generator。
  • await只能用在async函數之中,用在普通函數中會報錯。
  • await命令後面的 Promise 對象,運行結果可能是 rejected,所以最好把 await 命令放在 try…catch 代碼塊中。

當然我個人覺得下面寫法比較清晰點

//利用try...catch捕捉Promise的reject
async function ajax(data) {
	try {
		return await new Promise((resolve, reject) => {
			setTimeout(() => {
				console.log(data)
				resolve(data); //成功
			}, 2000);
		});
	} catch(err) {}
}
async function io() {
	try {
		const response = await new Promise((resolve, reject) => {
			setTimeout(() => {
				reject("io"); //失敗
			}, 1000);
		});
		//resolve執行纔會執行下面這句return 
		return response
	} catch(err) {
		console.log(err);
	}
}
//異步串行
(async() => {
	await ajax("ajax1");
	await ajax("ajax2");
	await io();
})()

(async() => {
    let [ajax1, ajax2] = await Promise.all([ajax("ajax1"), ajax("ajax2"),io()]);
    return [ajax1,ajax2]
})()

堆和棧

image

區別 堆(heap) 棧(stack)
結構 heap是沒有結構的,數據可以任意存放。heap用於複雜數據類型(引用類型)分配空間 stack是有結構的,每個區塊按照一定次序存放(後進先出),stack中主要存放一些基本類型的變量和對象的引用,存在棧中的數據大小與生存期必須是確定的。可以明確知道每個區塊的大小,因此,stack的尋址速度要快於heap
速度
圖示 image [外鏈圖片轉存失敗(img-BgaPuJ7W-1567235680991)(https://user-images.githubusercontent.com/17243165/51457554-c6d53800-1d8c-11e9-842b-6062d9fcbb37.png)]
類型 引用類型:對象,數組的內容 Boolean、Number、String、Undefined、Null,以及對象變量的指針
堆是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便 棧由系統自動分配,速度較快。但程序員是無法控制的
對堆而言,數據項位置沒有固定的順序。你可以以任何順序插入和刪除,因爲他們沒有頂部數據這一概念 對棧而言,棧中的新加數據項放在其他數據的頂部,移除時你也只能移除最頂部的數據(不能越位獲取)

使用new關鍵字初始化的之後是不存儲在棧內存中的。爲什麼呢?new大家都知道,根據構造函數生成新實例,這個時候生成的是對象,而不是基本類型。再看一個例子

var a = new String('123')
var b = String('123')
var c = '123'
console.log(a==b, a===b, b==c, b===c, a==c, a===c)  
>>> true false true true true false
console.log(typeof a)
>>> 'object'

我們可以看到new一個String,出來的是對象,而直接字面量賦值和工廠模式出來的都是字符串。但是根據我們上面的分析大小相對固定可預期的即便是對象也可以存儲在棧內存的,比如null,爲啥這個不是呢?再繼續看

var a = new String('123')
var b = new String('123')
console.log(a==b, a===b)
>>> false false

很明顯,如果a,b是存儲在棧內存中的話,兩者應該是明顯相等的,就像null === null是true一樣,但結果兩者並不相等,說明兩者都是存儲在堆內存中的,指針指向不一致

說到這裏,再去想一想我們常說的值類型和引用類型其實說的就是棧內存變量和堆內存變量,再想想值傳遞和引用傳遞、深拷貝和淺拷貝,都是圍繞堆棧內存展開的,一個是處理值,一個是處理指針

堆、棧、隊列之間的區別是?

  • 堆是在程序運行時,而不是在程序編譯時,申請某個大小的內存空間。即動態分配內存,對其訪問和對一般內存的訪問沒有區別。
  • 棧就是一個桶,後放進去的先拿出來,它下面本來有的東西要等它出來之後才能出來。(後進先出)
  • 隊列只能在隊頭做刪除操作,在隊尾做插入操作.而棧只能在棧頂做插入和刪除操作。(先進先出)

內存分配和垃圾回收

一般來說棧內存線性有序存儲,容量小,系統分配效率高。而堆內存首先要在堆內存新分配存儲區域,之後又要把指針存儲到棧內存中,效率相對就要低一些了

垃圾回收方面,棧內存變量基本上用完就回收了,而推內存中的變量因爲存在很多不確定的引用,只有當所有調用的變量全部銷燬之後才能回收

傳值和傳址

從一個向另一個變量複製引用類型的值,複製的其實是指針,因此兩個變量最終指向同一個對象。即複製的是棧中的地址而不是堆中的對象

從一個變量復向另一個變量複製基本類型的值,會創建這個值的副本

參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章