作者:万峥嵘 时间:2020-09-16
异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。
在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。 简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效果更高。
以上是关于异步的概念的解释,接下来我们通俗地解释一下异步:断续执行。
如果有异步方法会跳过,在等待回调结果后,在适当时机执行
在编程中,我们在处理一些简短、快速的操作时,例如计算 1 + 1 的结果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时接受多方面的请求。所以,当一个事件没有结束时,界面将无法处理其他请求。
为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。 为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。
回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。
setTimeout(function () {
console.log("蜡笔小新!");
}, 1000);
console.log("你真帅!");
由于 JavaScript 单线程的“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,并在将来的某个时间点触发一个函数调用。AJAX 就是典型的异步操作,如下:
function success(text) {
console.log(txt);
}
function fail(code) {
console.log(txt);
}
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
// 状态发生变化时,函数被回调
if (request.readyState === 4) {
// 成功完成
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
} else {
// HTTP请求还在继续...
}
};
request.open("GET", "http://...");
request.send();
把回调函数success(request.responseText)
和fail(request.status)
写到一个 AJAX 操作里很正常,但是不好看,而且不利于代码复用。
更好的写法:统一执行 AJAX 逻辑,不关心如何处理结果,然后根据结果是成果还是失败在将来的某个时候在调用响应的处理函数。实现执行代码和处理结果的分离。
var ajax = ajaxGet("http://...");
ajax.ifSuccess(success).ifFail(fail);
回调地狱:多层回调函数嵌套
method1(function (err, result) {
if (err) {
throw err;
}
method2(function (err, result) {
if (err) {
throw err;
}
method3(function (err, result) {
if (err) {
throw err;
}
method4(function (err, result) {
if (err) {
throw err;
}
method5(result);
});
});
});
});
Promise 是对回调地狱的思考,或者说是改良方案。它是在 async 函数普及之前唯一的通用性规范,最早是在 CommonJs 社区被提出来,当时比较被接受的是 Promise/A 规范。后来在此基础上提出了 Promise/A+规范,也就是现在业内推行的规范,ES6 也采用这种规范。
promise 这个词意味着”承诺“一个暂时还没有完成但将来会完成的事,与 Promise 进行交互的最主要的方法是,通过将函数传入它的 then 函数从而获得 Promise 的最终结果,告诉下一个 then 函数如何操作。
Promise 最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了。
Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。 由于 Promise 是 ES6 新增加的,所以一些旧的浏览器并不支持,苹果的 Safari 10 和 Windows 的 Edge 14 版本以上浏览器才开始支持 ES6 特性。
以下是 Promise 浏览器支持的情况:
new Promise(function (resolve, reject) {
// do somesthing...
});
示例比较:
//正常书写
setTimeout(function () {
console.log("我");
setTimeout(function () {
console.log("很");
setTimeout(function () {
console.log("帅");
}, 3000);
}, 4000);
}, 1000);
new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("我");
resolve();
}, 1000);
})
.then(function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("很");
resolve();
}, 4000);
});
})
.then(function () {
setTimeout(function () {
console.log("帅");
}, 3000);
});
Promise 构造函数只有一个参数,是一个函数,这个函数在构造之后会直接执行,所以我们称之为起始函数。起始函数包含两个参数 resolve 和 reject。 当 Promise 被构造时,起始函数会被异步执行。 resolve 和 reject 都是函数,其中调用 resolve 代表一切正常,reject 是出现异常时所调用的:
new Promise(function (resolve, reject) {
var a = 0;
var b = 1;
if (b == 0) reject("Diveide zero");
else resolve(a / b);
})
.then(function (value) {
console.log("a / b = " + value);
})
.catch(function (err) {
console.log(err);
})
.finally(function () {
console.log("End");
});
Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。 .then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列。
new Promise(function (resolve, reject) {
console.log(1111);
resolve(2222);
})
.then(function (value) {
console.log(value);
return 3333;
})
.then(function (value) {
console.log(value);
throw "An error";
})
.catch(function (err) {
console.log(err);
});
resolve() 中可以放置一个参数用于向下一个 then 传递一个值,then 中的函数也可以返回一个值传递给 then。但是,如果 then 中返回的是一个 Promise 对象,那么下一个 then 将相当于对这个返回的 Promise 进行操作,这一点从刚才的计时器的例子中可以看出来。
reject() 参数中一般会传递一个异常给之后的 catch 函数用于处理异常。
但是请注意以下两点:
上述的 "计时器" 程序看上去比函数瀑布还要长,所以我们可以将它的核心部分写成一个 Promise 函数:
function print(delay, message) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(message);
resolve();
}, delay);
});
}
然后实现功能:
print(1000, "First")
.then(function () {
return print(4000, "Second");
})
.then(function () {
print(3000, "Third");
});
//异步函数(async function)是 ECMAScript 2017 (ECMA-262) 标准的规范,几乎被所有浏览器所支持,除了 Internet Explorer。
async function asyncFunc() {
await print(1000, "First");
await print(4000, "Second");
await print(3000, "Third");
}
asyncFunc();
这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。
其实 Promise 规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+。ES6 中采用了 Promise/A+ 规范。
// 先定义三个常量表示状态
var PENDING = "pending";
var FULFILLED = "fulfilled";
var REJECTED = "rejected";
function MyPromise(fn) {
this.status = PENDING; // 初始状态为pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
// 构造函数里面添加两个数组存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 存一下this,以便resolve和reject里面访问
var that = this;
// resolve方法参数是value
function resolve(value) {
if (that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
// resolve里面将所有成功的回调拿出来执行
that.onFulfilledCallbacks.forEach((callback) => {
callback(that.value);
});
}
}
// reject方法参数是reason
function reject(reason) {
if (that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
// resolve里面将所有失败的回调拿出来执行
that.onRejectedCallbacks.forEach((callback) => {
callback(that.reason);
});
}
}
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
function resolvePromise(promise, x, resolve, reject) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
// 这是为了防止死循环
if (promise === x) {
return reject(
new TypeError("The promise and the return value are the same")
);
}
if (x instanceof MyPromise) {
// 如果 x 为 Promise ,则使 promise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
// 这个if跟下面判断then然后拿到执行其实重复了,可有可无
x.then(function (y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
}
// 如果 x 为对象或者函数
else if (typeof x === "object" || typeof x === "function") {
// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
if (x === null) {
return resolve(x);
}
try {
// 把 x.then 赋值给 then
var then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (typeof then === "function") {
var called = false;
// 将 x 作为函数的作用域 this 调用之
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
// 名字重名了,我直接用匿名函数了
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
function (y) {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
function (r) {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// 如果onFulfilled不是函数,给一个默认函数,返回value
// 后面返回新promise的时候也做了onFulfilled的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
var realOnFulfilled = onFulfilled;
if (typeof realOnFulfilled !== "function") {
realOnFulfilled = function (value) {
return value;
};
}
// 如果onRejected不是函数,给一个默认函数,返回reason的Error
// 后面返回新promise的时候也做了onRejected的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
var realOnRejected = onRejected;
if (typeof realOnRejected !== "function") {
realOnRejected = function (reason) {
throw reason;
};
}
var that = this; // 保存一下this
if (this.status === FULFILLED) {
var promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
if (typeof onFulfilled !== "function") {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if (this.status === REJECTED) {
var promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
if (typeof onRejected !== "function") {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
// 如果还是PENDING状态,将回调保存下来
if (this.status === PENDING) {
var promise2 = new MyPromise(function (resolve, reject) {
that.onFulfilledCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onFulfilled !== "function") {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
that.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onRejected !== "function") {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
});
return promise2;
}
};
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
MyPromise.resolve = function (parameter) {
if (parameter instanceof MyPromise) {
return parameter;
}
return new MyPromise(function (resolve) {
resolve(parameter);
});
};
MyPromise.reject = function (reason) {
return new MyPromise(function (resolve, reject) {
reject(reason);
});
};
MyPromise.all = function (promiseList) {
var resPromise = new MyPromise(function (resolve, reject) {
var count = 0;
var result = [];
var length = promiseList.length;
if (length === 0) {
return resolve(result);
}
promiseList.forEach(function (promise, index) {
MyPromise.resolve(promise).then(
function (value) {
count++;
result[index] = value;
if (count === length) {
resolve(result);
}
},
function (reason) {
reject(reason);
}
);
});
});
return resPromise;
};
MyPromise.race = function (promiseList) {
var resPromise = new MyPromise(function (resolve, reject) {
var length = promiseList.length;
if (length === 0) {
return resolve();
} else {
for (var i = 0; i < length; i++) {
MyPromise.resolve(promiseList[i]).then(
function (value) {
return resolve(value);
},
function (reason) {
return reject(reason);
}
);
}
}
});
return resPromise;
};
MyPromise.prototype.catch = function (onRejected) {
this.then(null, onRejected);
};
MyPromise.prototype.finally = function (fn) {
return this.then(
function (value) {
return MyPromise.resolve(fn()).then(function () {
return value;
});
},
function (error) {
return MyPromise.resolve(fn()).then(function () {
throw error;
});
}
);
};
MyPromise.allSettled = function (promiseList) {
return new MyPromise(function (resolve) {
var length = promiseList.length;
var result = [];
var count = 0;
if (length === 0) {
return resolve(result);
} else {
for (var i = 0; i < length; i++) {
(function (i) {
var currentPromise = MyPromise.resolve(promiseList[i]);
currentPromise.then(
function (value) {
count++;
result[i] = {
status: "fulfilled",
value: value,
};
if (count === length) {
return resolve(result);
}
},
function (reason) {
count++;
result[i] = {
status: "rejected",
reason: reason,
};
if (count === length) {
return resolve(result);
}
}
);
})(i);
}
}
});
};
module.exports = MyPromise;
event loop 是一个执行模型,在不同的地方有不同的实现。浏览器和 NodeJS 基于不同的技术实现了各自的 Event Loop。
JavaScript 语言就采用 event loop,来解决单线程运行带来的一些问题。如图:
简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种 I/O 操作)的通信,被称为"Event Loop 线程"(可以译为"消息线程")。
每当遇到 I/O 的时候,主线程就让 Event Loop 线程去通知相应的 I/O 程序,然后接着往后运行,所以不存在红色的等待时间。等到 I/O 程序完成操作,Event Loop 线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。
可以看到,由于多出了橙色的空闲时间,所以主线程得以运行更多的任务,这就提高了效率。这种运行方式称为"异步模式"(asynchronous I/O)。
这种机制与 android 的 handler 机制类似,如图(仅供参考):
宏队列,microtask,也叫 tasks。 一些异步任务的回调会依次进入 macro task queue,等待后续被调用,这些异步任务包括
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
});
new Promise((resolve, reject) => {
console.log(4);
resolve(5);
}).then((data) => {
console.log(data);
});
setTimeout(() => {
console.log(6);
});
console.log(7);
结果输出:
// 正确答案
1;
4;
7;
5;
2;
3;
6;
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
});
new Promise((resolve, reject) => {
console.log(4);
resolve(5);
}).then((data) => {
console.log(data);
Promise.resolve()
.then(() => {
console.log(6);
})
.then(() => {
console.log(7);
setTimeout(() => {
console.log(8);
}, 0);
});
});
setTimeout(() => {
console.log(9);
});
console.log(10);
// 正确答案
1;
4;
10;
5;
6;
7;
2;
3;
9;
8;