近日,整理了學習筆記,而後分享給你們,共同窗習,共同進步,篇幅過長建議收藏。css
js異步編程咱們從如下幾個部分來看一看html
首先咱們得清楚的知道,什麼是同步,什麼是異步?前端
通俗的來說,同步指的是 調用以後獲得的結果,才能夠去作別的任務node
而異步指的是 調用以後先無論結果,繼續再幹別的任務git
在深一步瞭解異步相關內容以前咱們先來了解兩個重要的概念。github
進程是程序運行的實例,同一個程序能夠產生多個進程,一個進程有能夠包含一個或者多個線程web
線程是操做系統可以運算調度的最小單位,一次只能執行一個任務,有本身的調用棧,寄存器環境,同一進程的線程共享進程資源。ajax
那麼,問題就又來了,咱們知道JS是單線程的,那麼單線程的JS又是如何實現一步的呢?chrome
答案呢,其實就是單線程的JS是經過瀏覽器內核多線程實現異步編程
咱們來深刻的瞭解一下瀏覽器相關工做原理吧。
以開源的Chromium爲例
瀏覽器的進程
渲染進程又包含
渲染布局(頁面的html,css,js,構建DOM樹和渲染樹就是GUI線程的工做)
解析、執行JS程序(chrome v8引擎),JS引擎線程只有一個,這也是爲何咱們所說的js是單線程的緣由,其實呢語言是沒有單線程多線程之說的,由於解釋這個語言的引擎是單線程的,因此咱們說js是單線程的
JS引擎線程與GUI線程互斥,由於JS引擎線程也能夠操做DOM,容易形成混亂
儘可能控制js文件的大小,不要讓js執行時間太長
setTimeout
setInterval
將知足觸發條件的事件放入任務隊列(異步事件放入任務隊列)
處理ajax請求的線程
XHR所在線程
若是請求完成時有回調函數,它就會通知事件觸發線程往任務隊列裏面添加事件
知道了瀏覽器是如何進行工做的以後,咱們回顧一下前端常見的有哪些異步場景?
Event Loop
順序以下:
1.調用webApi(setTimeout)
2.定時器線程計數2s
3.事件觸發線程將定時器事件放入任務隊列(往執行棧中加任務)
4.主線程經過EventLoop遍歷任務隊列(往執行棧中出任務)
複製代碼
// for 循環是同步任務,因此等for 執行完以後纔會去執行定時器這個異步任務, var做用域是全局的,沒有塊級做用域
for (var i = 1; i <= 10; i ++) {
setTimeout(function() {
console.log(i)
}, 1000 * i)
}
複製代碼
// 利用函數閉包構建做用域
for (var i = 1; i <= 10; i ++) {
(function(i) {
setTimeout(function() {
console.log(i)
}, 1000 * i)
})(i)
}
複製代碼
// ES6新增的let 塊級做用域
for (let i = 1; i <= 10; i ++) {
setTimeout(function() {
console.log(i)
}, 1000 * i)
}
複製代碼
主要根據在瀏覽器環境下的Event Loop來講明,node.js的EventLoop 後續node部分會講到。
首先,咱們要明白異步是怎麼去實現的:
異步的任務有兩類
Event Loop執行順序
注意
一個Event Loop 有一個或多個task queue
每一個Event Loop 有一個 Microtask queue
首先呢,咱們要先去知道,異步編程的方法有哪些?
咱們一個一個的來說述,回調函數你們很經常使用,直接開始事件發佈/訂閱這個異步編程方法。
那麼如何理解發布/訂閱呢?
有三個核心概念咱們得了解一下
1. 首先發布者 發佈消息到 事件中心
2. 而後訂閱者 從事件中心 訂閱消息
3. 訂閱者能夠有多個
複製代碼
如何實現一個事件的發佈/訂閱呢?
class PubSub {
constructor() {
// 用對象來存儲,是由於事件的名字和事件的處理函數用對象能夠很方便的對應起來
this.events = {}
}
publish(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(cb => {
cb.apply(this, data)
})
}
}
subscribe(eventName, cb) {
if (this.events[eventName]) {
this.events[eventName].push(cb)
} else {
this.events[eventName] = [cb]
}
}
// 取消訂閱
unSubscribe(eventName, cb) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(item => item !== cb)
}
}
}
複製代碼
優勢
缺點
咱們先來看一看promise/A+ 規範。
一個有then方法的對象或者函數,行爲符合本規範
一個定義了then方法的對象或函數
任何JavaScript的合法值
throw語句拋出的值
表示一個promise的拒絕緣由
state: pending
1. value --> state: fulfilled
2. reason --> state: rejected
複製代碼
一個promise必須提供一個then方法來訪問其當前值、終值和拒因
promise的then方法接受兩個參數
const promise2 = promise1.then(onFulfilled, onRejected)
複製代碼
onFulfilled 和 onRejected都是可選參數
若是onFulfilled不是函數,其必須被忽略
若是onRejected不是函數,其必須被忽略
onFulfilled 不是函數,promise1的狀態是fulfilled
state:fulfilled
value:同promise1
onFulfilled 不是函數,promise1的狀態是rejected
state:rejected
value:同promise1
onFulfilled或者onRejected 是一個函數
return x
進入解析過程
複製代碼
若是onFulfilled是函數:
當promise執行結束後其必須被調用,其第一個參數爲promise的終值
在promise執行結束前其不可被調用
其調用次數不可超過一次
若是onRejected是函數:
當promise被拒絕執行後其必須被調用,其第一個參數爲promise的拒因
在promise被拒絕執行前其不可被調用
其調用次數不可超過一次
then方法能夠被同一個promise調用屢次
promise
成功執行時,全部 onFulfilled
需按照其註冊順序依次回調promise
被拒絕執行時,全部的 onRejected
需按照其註冊順序依次回調const promise2 = promise1.then(onFulfilled, onRejected)
複製代碼
onFulfilled 不是函數,promise1的狀態是fulfilled
state:fulfilled
value:同promise1
onFulfilled 不是函數,promise1的狀態是rejected
state:rejected
value:同promise1
onFulfilled或者onRejected 是一個函數
return x
進入解析過程
複製代碼
先抽象出一個模型resolove(promise, x)
若是 promise
和 x
指向同一對象,以 TypeError
爲據因拒絕執行 promise
若是 x
爲 Promise ,則使 promise
接受 x
的狀態
x
處於等待態, promise
需保持爲等待態直至 x
被執行或拒絕x
處於執行態,用相同的值執行 promise
x
處於拒絕態,用相同的據因拒絕 promise
若是 x
爲對象或者函數:
把 x.then
賦值給 then
若是取 x.then
的值時拋出錯誤 e
,則以 e
爲據因拒絕 promise
若是then
是函數,將x
做爲函數的做用域this
調用之。傳遞兩個回調函數做爲參數,第一個參數叫作resolvePromise
,第二個參數叫作rejectPromise
:
resolvePromise
以值 y
爲參數被調用,則運行 [[Resolve]](promise, y)
rejectPromise
以據因 r
爲參數被調用,則以據因 r
拒絕 promise
resolvePromise
和 rejectPromise
均被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用若是調用then
方法方法拋出了異常e
resolvePromise
或 rejectPromise
已經被調用,則忽略之e
爲據因拒絕 promise
若是 then
不是函數,以 x
爲參數執行 promise
若是 x
不爲對象或者函數,以 x
爲參數執行 promise
那麼咱們來實現如下這個解析過程吧
import { isObject, isFunction } from "util"
//! promise 解析過程
function resolve(promise, x) {
if (x === promise) {
return reject(promise, new TypeError('cant be the same'))
}
if (isPromise(x)) {
if (x.state === 'pending') {
return x.then(() => {
resolve(promise, x.value)
}, () => {
reject(promise, x.value)
})
}
if (x.state === 'fulfilled') {
return fulfill(promise, x.value)
}
if (x.state === 'rejected') {
return reject(promise, x.value)
}
} else if (isObject(x) || isFunction(x)) {
let then;
try {
then = x.then
} catch (e) {
return reject(promise, e)
}
if (isFunction(then)) {
let isCalled = false;
try {
then.call(x, function resolvePromise(y) {
if (isCalled) {
return
}
isCalled = true
resolve(promise, y)
}, function rejectPromise(r) {
if (isCalled) {
return
}
isCalled = true
reject(promise, r)
})
} catch (e) {
if (!isCalled) {
reject(promise, e)
}
}
} else {
return fulfill(promise, x)
}
} else {
return fulfill(promise, x)
}
}
複製代碼
接下來咱們來看一下ES6 Promise API
構造函數 | 說明 |
---|---|
new Promise(function(resolve, reject) { }) | 函數做爲參數 |
resolve函數將promise狀態從pending變成resolved(fulfilled) | |
reject函數將promise狀態從pending變成rejected |
方法 | 說明 |
---|---|
Promise.resolve(param) | 等同於 new Promise(function(resolve, reject){resolve(param)}) |
Promise.reject(reason) | 等同於 new Promise(function(resolve, reject){reject(reason)}) |
Promise.all([p1,...,pn]) | 輸入一組promise返回一個新的promise,所有promise都是fulfilled結果纔是fulfilled狀態 |
Promise.allSettled([p1,...,pn]) | 輸入一組promise返回一個新的promise,全部的promise都是fulfilled結果纔是fulfilled狀態 |
Promise.race([p1,...,pn]) | 輸入一組promise返回一個新的promise,結果promise的狀態根據第一個變化的promise狀態 |
方法 | 說明 |
---|---|
promise.then(onFulfilled,onRejected) | promise 狀態改變以後的回調,返回新的promise對象 |
promise.catch(function(reason) {}) | 同promise.then(null, onRejected),promise狀態爲rejected的回調 |
promise.finally(function(reason) { // test}) | 同promise.then(function(){ // test}, function(){ // test}),無論promise狀態如何都會執行 |
注意點
因此說寫catch的時候只須要在鏈式調用的最後面加一個catch語句去捕獲就能夠。
**題目:**3秒以後亮一次紅燈,再過兩秒亮一次綠燈,再過一秒亮一次黃燈,用promise 實現屢次交替亮燈的效果,console.log 模擬亮燈
// 思路拆解:
// 1.多少秒後亮某個顏色的燈
// 2.順序亮一批燈
// 3.循環順序亮一批燈
function light(color, second) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log(color)
resolve()
}, second * 1000)
})
}
function orderLights(list) {
let promise = Promise.resolve()
list.forEach(item => {
promise = promise.then(() => {
return light(item.color, item.second)
})
})
promise.then(function() {
return orderLights(list)
})
}
const list = [
{color: 'red', second: 3},
{color: 'green', second: 2},
{color: 'yellow', second: 1},
]
orderLights(list)
複製代碼
首先先來看兩個概念
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = i >= items.length
var value = !done ? items[i++] : undefined
return {
done,
value
}
}
}
}
複製代碼
Generator函數(生成器)
執行Generator函數生成一個生成器對象
只能出如今Generator函數裏
用來暫停和恢復生成器函數
next執行
next參數
function* createGenerator() {
let first = yield 1
let second = yield first + 2
yield second + 3
}
let gen = createGenerator();
let g1 = gen.next() // {value: 1, done: false}
let g2 = gen.next(4) // {value: 6, done: false}
let g3 = gen.next(5) // {value: 8, done: false}
let g4 = gen.next() // {value: undefined, done: true}
複製代碼
yield生成器函數 / 可迭代對象
舉個栗子
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield 100;
yield* generator1();
yield 200;
}
let g = generator2()
g.next() //? { value: 100, done: false }
g.next() //? { value: 1, done: false }
g.next() //? { value: 2, done: false }
g.next() //? { value: 200, done: false }
g.next() //? { value: undefined, done: true }
複製代碼
return(param)
//! return(param)
function* createIterator() {
yield 1;
yield 2;
yield 3;
}
let iterator = createIterator();
iterator.next(); // { value: 1, done: false }
iterator.return();// { value: undefined, done: true }
iterator.next();// { value: undefined, done: true }
複製代碼
throw(param)
//! throw(param)
function* createIterator() {
let first = yield 1;
let second;
try{
second = yield first + 2;
} catch (e) {
second = 6;
}
yield second + 3
}
let iterator = createIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(10)); // { value: 12, done: false }
console.log(iterator.throw(new Error('error'))); // { value: 9, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
複製代碼
接下來咱們來看一下generator函數的實現原理
首先先得了解找一個概念
耦合程度仍是挺高的
舉個栗子
const fs = require('fs');
const Thunk = function(fn) {
return function(...args) {
return function(callback) {
return fn.call(this, ...args, callback)
}
}
}
const readFileThunk = Thunk(fs.readFile);
function run(fn) {
var gen = fn();
function next(err, data) {
var result =gen.next(data);
if (result.done) return;
result.value(next);
}
next()
}
const g = function*() {
const s1 = yield readFileThunk('./g1.json')
console.log(s1.toString());
const s2 = yield readFileThunk('./g2.json')
console.log(s2.toString());
const s3 = yield readFileThunk('./g3.json')
console.log(s3.toString());
}
run(g);
複製代碼
co模塊是generator函數的自動執行器,功能相似與Thunk函數做用
co源碼:github.com/tj/co
async function test() {
return 1;
}
const p = test();
console.log(p); // Promise { 1 }
p.then(function (data) {
console.log(data) // 1
})
async function test() {
throw new Error('error')
}
const p = test();
console.log(p);
p.catch(function (data) {
console.log(data)
})
複製代碼
await
1. promise
1> resolved 返回promise的值
2> rejected 拋出promise的拒因
2. 非promise
1> 返回對應的值 await 1
複製代碼
Generator + 自動執行器
// async函數實現原理
async function example(params) {
// ...
}
function example(params) {
return spawn(function*() {
// ...
})
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF(); // 生成器對象
function step(nextF) {
let next;
try {
next = nextF(); //執行gen.next()
} catch (e) {
return reject(e)
}
if (next.done) {
return resolve(next.value)
}
//* next.done 爲 false時,繼續step;
Promise.resolve(next.value).then(
function(v) {
step(function() {
return gen.next(v)
})
},
function(e) {
stop(function() {
return gen.throw(e)
})
}
)
}
step(function() {
return gen.next(undefined)
})
})
}
複製代碼
整理的過程當中,不免會有疏漏,如果看到有誤或者須要補充的知識點,歡迎留言小編