大家好,我是小雨小雨,致力于分享有趣的、實(shí)用的技術(shù)文章。
內(nèi)容分為翻譯和原創(chuàng),如果有問題,歡迎隨時(shí)評(píng)論或私信,希望和大家一起進(jìn)步。
分享不易,希望能夠得到大家的支持和關(guān)注。
TL;DR
async是generator和promise的語(yǔ)法糖,利用迭代器的狀態(tài)機(jī)和promise來(lái)進(jìn)行自更新!
如果懶得往下看,可以看下這個(gè)極其簡(jiǎn)易版本的實(shí)現(xiàn)方式:
// 復(fù)制粘貼即可直接運(yùn)行
function stateMac (arr) {
let val;
return {
next(){
if ((val = arr.shift())) {
return {
value: val,
done: false
}
} else {
return {
done: true
}
}
}
}
}
function asyncFn(arr) {
const iterator = stateMac(arr);
function doSelf () {
const cur = iterator.next();
const value = cur.value;
if (cur.done) {
console.log('done');
return;
}
switch (true) {
case value.then && value.toString() === '[object Promise]':
value.then((result) => {
console.log(result);
doSelf();
})
break;
case typeof value === 'function':
value();
doSelf();
break;
default:
console.log(value);
doSelf();
}
}
doSelf();
}
const mockAsync = [
1,
new Promise((res) => {
setTimeout(function () {
res('promise');
}, 3000);
}),
function () {
console.log('測(cè)試');
}
];
console.log('開始');
asyncFn(mockAsync);
console.log('結(jié)束');
前言
async & await 和我們的日常開發(fā)緊密相連,但是你真的了解其背后的原理嗎?
本文假設(shè)你對(duì)promise、generator有一定了解。
簡(jiǎn)述promise
promise就是callback的另一種寫法,避免了毀掉地獄,從橫向改為縱向,大大提升了可讀性和美觀。
至于promise的實(shí)現(xiàn),按照promise A+規(guī)范一點(diǎn)點(diǎn)寫就好了,完成后可以使用工具進(jìn)行測(cè)試,確保你的寫的東西是符合規(guī)范的。
具體實(shí)現(xiàn)原理,市面上有各種各樣的寫法,我就不多此一舉了。
簡(jiǎn)述generator
generator就不像promise那樣,他改變了函數(shù)的執(zhí)行方式。可以理解為協(xié)程,就是說多個(gè)函數(shù)互相配合完成任務(wù)。類似于這個(gè)東西:
function generator() {
return {
_value: [1, 2, 3, 4],
next() {
return {
value: this._value.shift(),
done: !this._value.length
};
}
};
}
const it = generator();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
這只是一個(gè)demo,僅供參考。
具體請(qǐng)參考MDN.
async & await
照我的理解,其實(shí)就是generator和promise相交的產(chǎn)物,被解析器識(shí)別,然后轉(zhuǎn)換成我們熟知的語(yǔ)法。
這次要做的就是去看編譯之后的結(jié)果是什么樣的。
既然如此,我們就帶著問題去看,不然看起來(lái)也糟心不是~
async包裝的函數(shù)會(huì)返回一個(gè)什么樣的promise?
// 源代碼:
async function fn() {}
fn();
// 編譯后變成了一大坨:
// generator的polyfill
require("regenerator-runtime/runtime");
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function fn() {
return _fn.apply(this, arguments);
}
function _fn() {
_fn = _asyncToGenerator(
/*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
case "end":
return _context.stop();
}
}
}, _callee);
})
);
return _fn.apply(this, arguments);
}
fn();
內(nèi)容也不是很多,我們一點(diǎn)點(diǎn)來(lái)看:
generator包裝
fn內(nèi)部調(diào)用的是_fn,一個(gè)私有方法,使用的apply綁定的this,并傳入了動(dòng)態(tài)參數(shù)。
_fn內(nèi)調(diào)用了_asyncToGenerator方法,由于js調(diào)用棧后進(jìn)先出:
讀起來(lái)是這樣的:fn() => _asyncToGenerator => .mark()
執(zhí)行是反過來(lái)的:.mark() => _asyncToGenerator => fn()
我們先往里看,映入眼簾的是regeneratorRuntime.mark,該方法是generator的polyfill暴露的方法之一,我們?nèi)?nèi)部(require('regenerator-runtime/runtime'))簡(jiǎn)單看下這個(gè)mark是用來(lái)干什么的。
// 立即執(zhí)行函數(shù),適配commonjs和瀏覽器
(function (exports) {
// 暴露mark方法
exports.mark = function (genFun) {
// 兼容判斷__proto__,處理老舊環(huán)境
if (Object.setPrototypeOf) {
Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
} else {
genFun.__proto__ = GeneratorFunctionPrototype;
// 設(shè)置Symbol.toStringTag,適配toString
if (!(toStringTagSymbol in genFun)) {
genFun[toStringTagSymbol] = 'GeneratorFunction';
}
}
// 設(shè)置原型
genFun.prototype = Object.create(Gp);
return genFun;
};
})(typeof module === 'Object' ? module.exports : {});
mark做了兩個(gè)操作,一個(gè)是設(shè)置genFun的proto,一個(gè)是設(shè)置prototype,可能有人會(huì)好奇:
proto不是對(duì)象上的嗎?prototype不是函數(shù)上的嗎?為啥兩個(gè)同時(shí)應(yīng)用到一個(gè)上面了
這樣操作是沒問題的,genFun不僅是函數(shù)啊,函數(shù)還是對(duì)象,js中萬(wàn)物皆對(duì)象哦。你想想是不是可以通過Function構(gòu)造函數(shù)new出一個(gè)函數(shù)?
然后開始設(shè)置proto和prototype,在次之前,我們來(lái)簡(jiǎn)單捋一下原型。
原型
下面是個(gè)人理解的一個(gè)說法,未查閱v8引擎,但是這樣是說得通的。如果有問題,歡迎指出,一起溝通,我也會(huì)及時(shí)修改,以免誤導(dǎo)他人?。?!。
首先要知道這三個(gè)的概念:搞清對(duì)象的原型對(duì)象(proto)、構(gòu)造函數(shù)的原型(prototype)、構(gòu)造方法(constructor)。
方便記憶,只需要記住下面幾條即可:
- prototype是構(gòu)造函數(shù)(注意:構(gòu)造函數(shù)也是對(duì)象嗷)上特有的屬性,代表構(gòu)造函數(shù)的原型。舉個(gè)例子:
有一位小明同學(xué)(指代構(gòu)造函數(shù)),他有自己的朋友圈子(指代prototype),通過小明可以找到小紅(構(gòu)造函數(shù).prototype.小紅),在通過小紅的朋友圈子(prototype)還能找到小藍(lán),直到有一個(gè)人(指代null),孑然一身、無(wú)欲無(wú)求,莫得朋友。
上面這個(gè)關(guān)系鏈就可以理解為原型鏈。
- proto是每一個(gè)對(duì)象上特有的屬性,指向當(dāng)前對(duì)象構(gòu)造函數(shù)的prototype。再舉個(gè)例子:
小明家里催的急,不就就生了個(gè)大胖小子(通過構(gòu)造函數(shù){小明}創(chuàng)造出對(duì)象{大胖小子}),可以說這個(gè)大胖小子一出生就被眾星捧月,小明的朋友們紛紛表示,以后孩子有啥事需要幫忙找我就成。這就指代對(duì)象上的__proto__,__proto__可以引用構(gòu)造函數(shù)的任何關(guān)系。
所以說,代碼源于生活~
constructor是啥呢,就是一個(gè)prototype上的屬性,表示這個(gè)朋友圈子是誰(shuí)的,對(duì)于小明來(lái)說: 小明.prototype.constructor === 小明。所以,當(dāng)我們進(jìn)行繼成操作的時(shí)候,有必要修正一下constructor,不然朋友圈子就亂了~
js中函數(shù)和對(duì)象有點(diǎn)套娃的意思,萬(wàn)物皆對(duì)象,對(duì)象又是從構(gòu)造函數(shù)構(gòu)造而來(lái)。對(duì)于小明來(lái)說,就是我生我生我~~
來(lái)看兩個(gè)判斷:
proto 指向構(gòu)造當(dāng)前對(duì)象的構(gòu)造函數(shù)的prototype,由于萬(wàn)物皆對(duì)象,對(duì)象又是通過構(gòu)造函數(shù)構(gòu)造而來(lái)。故Object通過Function構(gòu)造而來(lái),所以指向了Function.prototype
console.log(Object.__proto__ === Function.prototype); // => true
proto 指向構(gòu)造當(dāng)前對(duì)象的構(gòu)造函數(shù)的prototype,由于萬(wàn)物皆對(duì)象,對(duì)象又是通過構(gòu)造函數(shù)構(gòu)造而來(lái)。故Function通過Function構(gòu)造而來(lái),所以指向了Function.prototype
console.log(Function.__proto__ === Function.prototype); // => true
有興趣的朋友可以再看看這篇文章
然后,我們?cè)賮?lái)看看這張圖,跟著箭頭走一遍,是不是就很清晰了?
繼續(xù)generator包裝
mark方法會(huì)指定genFun的proto和prototype,完完全全替換了genFun的朋友圈以及創(chuàng)造genFun的構(gòu)造函數(shù)的朋友圈,現(xiàn)在genFun就是Generator的克隆品了。
用來(lái)設(shè)置proto 和 prototype的值,GeneratorFunctionPrototype,GP,我們也簡(jiǎn)單過一下:
// 創(chuàng)建polyfill對(duì)象
var IteratorPrototype = {};
IteratorPrototype[iteratorSymbol] = function () {
return this;
};
// 原型相關(guān)操作
// 獲取對(duì)象的原型: __proto__
var getProto = Object.getPrototypeOf;
// 原生iterator原型
var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
// IteratorPrototype設(shè)置為原生
if (
NativeIteratorPrototype &&
NativeIteratorPrototype !== Op &&
hasOwn.call(NativeIteratorPrototype, iteratorSymbol)
) {
// This environment has a native %IteratorPrototype%; use it instead
// of the polyfill.
IteratorPrototype = NativeIteratorPrototype;
}
// 創(chuàng)造原型
// Gp 為 迭代器原型
// IteratorPrototype作為原型對(duì)象
var Gp = (GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(
IteratorPrototype
));
// 更新構(gòu)造函數(shù)和原型
GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
GeneratorFunctionPrototype.constructor = GeneratorFunction;
// toString,調(diào)用Object.toString.call的時(shí)候會(huì)返回GeneratorFunction
GeneratorFunctionPrototype[
toStringTagSymbol
] = GeneratorFunction.displayName = 'GeneratorFunction';
最后再返回經(jīng)過處理的genFun,然后再回到mark函數(shù)外~
_asyncToGenerator
_asyncToGenerator 接收mark處理過的結(jié)果:
// fn 為 generator 的克隆品
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
// 調(diào)用_callee,先看下面,一會(huì)在回來(lái)哈~
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(
gen,
resolve,
reject,
_next,
_throw,
'next',
value
);
}
function _throw(err) {
asyncGeneratorStep(
gen,
resolve,
reject,
_next,
_throw,
'throw',
err
);
}
_next(undefined);
});
};
}
regeneratorRuntime.wrap
上面的_asyncToGenerator執(zhí)行后,會(huì)執(zhí)行mark返回的函數(shù):
function _callee() {
return regeneratorRuntime.wrap(function _callee$(
_context
) {
// 這里就是動(dòng)態(tài)得了,也就是根據(jù)用戶寫的async函數(shù),轉(zhuǎn)換的記過,由于我們是一個(gè)空函數(shù),所以直接stop了
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
case 'end':
return _context.stop();
}
}
},
_callee);
}
_callee會(huì)返回wrap處理后的結(jié)果,我們繼續(xù)看:
// innerFn是真正執(zhí)行的函數(shù),outerFn為被mark的函數(shù)
// self, tryLocsList未傳遞,為undefined
function wrap(innerFn, outerFn, self, tryLocsList) {
// If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
// outerFn 的原型已經(jīng)被 mark重新設(shè)置,所以會(huì)包含generator相關(guān)原型
var protoGenerator =
outerFn && outerFn.prototype instanceof Generator
? outerFn
: Generator;
// 創(chuàng)建自定義原型的對(duì)象
var generator = Object.create(protoGenerator.prototype);
// context 實(shí)例是包含的 this.tryEntries 的
var context = new Context(tryLocsList || []);
// The ._invoke method unifies the implementations of the .next,
// .throw, and .return methods.
generator._invoke = makeInvokeMethod(innerFn, self, context);
return generator;
}
其中有個(gè)new Context()的操作,用來(lái)重置并記錄迭代器的狀態(tài),后面會(huì)用到。
之后給返回generator掛載一個(gè)_invoke方法,調(diào)用makeInvokeMethod,并傳入self(未傳遞該參數(shù),為undefined)和context。
function makeInvokeMethod(innerFn, self, context) {
// state只有在該函數(shù)中備操作
var state = GenStateSuspendedStart; // GenStateSuspendedStart: 'suspendedStart'
// 作為外面的返回值
return function invoke(method, arg) {
// 這里就是generator相關(guān)的一些操作了,用到的時(shí)候再說
};
}
利用閉包初始化state,并返回一個(gè)invoke函數(shù),接受兩個(gè)參數(shù),方法和值。先看到這,繼續(xù)往后看。
回到之前的_asyncToGenerator:
// 返回帶有_invoke屬性的generator對(duì)象
var gen = fn.apply(self, args);
之后定義了一個(gè)next和throw方法,隨后直接調(diào)用_next開始執(zhí)行:
function _next(value) {
asyncGeneratorStep(
gen, // 迭代器函數(shù)
resolve, // promise的resolve
reject, // promise的project
_next, // 當(dāng)前函數(shù)
_throw, // 下面的_throw函數(shù)
'next', // method名
value // arg 參數(shù)值
);
}
function _throw(err) {
asyncGeneratorStep(
gen,
resolve,
reject,
_next,
_throw,
'throw',
err
);
}
_next(undefined);
其中都是用的asyncGeneratorStep,并傳遞了一些參數(shù)。
那asyncGeneratorStep又是啥呢:
function asyncGeneratorStep(
gen,
resolve,
reject,
_next,
_throw,
key,
arg
) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
// 出錯(cuò)
reject(error);
return;
}
if (info.done) {
// 如果完成,直接resolve
resolve(value);
} else {
// 否則,繼續(xù)下次next調(diào)用,形成遞歸
Promise.resolve(value).then(_next, _throw);
}
}
代碼很少,獲取即將要調(diào)用的方法名(key)并傳入?yún)?shù),所以當(dāng)前info即是:
var info = gen['next'](arg);
那next是哪來(lái)的那?就是之前mark操作中定義的,如果原生支持,就是用原生的迭代器提供的next,否則使用polyfill中定義的next。
還記得之前的makeInvokeMethod嗎?
它其實(shí)是用來(lái)定義標(biāo)準(zhǔn)化next、throw和return的:
function defineIteratorMethods(prototype) {
['next', 'throw', 'return'].forEach(function (method) {
prototype[method] = function (arg) {
return this._invoke(method, arg);
};
});
}
// Gp在之前的原型操作有用到
defineIteratorMethods(Gp);
然后當(dāng)我們執(zhí)行的時(shí)候,就會(huì)走到_invoke定義的invoke方法中:
function invoke(method, arg) {
// 狀態(tài)判斷,拋錯(cuò)
if (state === GenStateExecuting) {
throw new Error('Generator is already running');
}
// 已完成,返回done狀態(tài)
if (state === GenStateCompleted) {
if (method === 'throw') {
throw arg;
}
// Be forgiving, per 25.3.3.3.3 of the spec:
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
return doneResult();
}
// 這里就是之前定義的Context實(shí)例,下面代碼沒啥了,自己看吧
context.method = method;
context.arg = arg;
while (true) {
var delegate = context.delegate;
if (delegate) {
var delegateResult = maybeInvokeDelegate(delegate, context);
if (delegateResult) {
if (delegateResult === ContinueSentinel) continue;
return delegateResult;
}
}
if (context.method === 'next') {
// Setting context._sent for legacy support of Babel's
// function.sent implementation.
context.sent = context._sent = context.arg;
} else if (context.method === 'throw') {
if (state === GenStateSuspendedStart) {
state = GenStateCompleted;
throw context.arg;
}
context.dispatchException(context.arg);
} else if (context.method === 'return') {
context.abrupt('return', context.arg);
}
state = GenStateExecuting;
// innerFn就是while個(gè)循環(huán)了,使我們的代碼主體
var record = tryCatch(innerFn, self, context);
if (record.type === 'normal') {
// If an exception is thrown from innerFn, we leave state ===
// GenStateExecuting and loop back for another invocation.
state = context.done
? GenStateCompleted
: GenStateSuspendedYield;
if (record.arg === ContinueSentinel) {
continue;
}
return {
value: record.arg,
done: context.done
};
} else if (record.type === 'throw') {
state = GenStateCompleted;
// Dispatch the exception by looping back around to the
// context.dispatchException(context.arg) call above.
context.method = 'throw';
context.arg = record.arg;
}
}
};
在之后,就是我們熟悉的promise相關(guān)操作了,在判斷done是否為true,否則繼續(xù)執(zhí)行,將_next和_throw作為resolve和reject傳入即可。
小結(jié)
可以看到,僅僅一個(gè)async其實(shí)做了不少工作。核心就是兩個(gè),產(chǎn)出一個(gè)兼容版本的generator和使用promise,回到這節(jié)的問題上,答案就是:
return new Promise(function (resolve, reject) {});
沒錯(cuò),就是返回一個(gè)Promise,內(nèi)部會(huì)根據(jù)狀態(tài)及決定是否繼續(xù)執(zhí)行下一個(gè)Promise.resolve().then()。
如果async函數(shù)內(nèi)有很多其他操作的代碼,那么while會(huì)跟著變化,利用prev和next來(lái)管理執(zhí)行順序。這里就不具體分析了,自己寫個(gè)例子就明白了~
可以通過babel在線轉(zhuǎn)換,給自己一個(gè)具象的感知,更利于理解。
為什么下面這種函數(shù)外的console不會(huì)等待,函數(shù)內(nèi)的會(huì)等待?
async function fn() {
await (async () => {
await new Promise((r) => {
setTimeout(function () {
r();
}, 2000);
});
})();
console.log('你好');
}
fn();
console.log(123);
因?yàn)榻馕龊蟮腸onsole.log(123); 是在整個(gè)語(yǔ)法糖之外啊,log 和 fn 是主協(xié)程序,fn內(nèi)是輔協(xié)程。不相干的。
總結(jié)
有句話怎么說來(lái)著,會(huì)者不難,難者不會(huì)。所以人人都是大牛,只是你還沒發(fā)力而已,哈哈~
筆者后來(lái)思考覺得這種寫法完全就是回調(diào)函數(shù)的替代品,而且增加了空間,加深了調(diào)用堆棧,或許原生的寫法才是效率最高的吧。
不過,需要良好的編碼規(guī)范,算是一種折中的方式了。畢竟用這種方式來(lái)寫業(yè)務(wù)事半功倍~
對(duì)于本文觀點(diǎn),完全是個(gè)人閱讀后的思考,如有錯(cuò)誤,歡迎指正,我會(huì)及時(shí)更新,避免誤導(dǎo)他人。
拜了個(gè)拜~