JS的异步编程模式
在ES6未
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、pandas是什么?
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
二、使用步骤
1.引入库
代码如下(示例):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
2.读入数据
代码如下(示例):
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
该处使用的url网络请求的数据。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。推出异步编程模式前,通常用如下几种方式来编写异步代码
- 事件监听
- 回调函数
- 发布/订阅
ES6推出自己的异步模式之后又新增了如下的模式
- Promise
- Generator/yield
- async/await
在学习ES6的异步模式之前,有必要回顾一下老的模式,了解其优缺点,以便更好的理解新模式推出的意义
回调函数
这是异步编程最基本的方法。
假定有两个函数f1和f2,后者等待前者的执行结果。
f1();
f2();
如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。
function f1(callback){
setTimeout(function () {
// f1的任务代码
callback();
}, 1000);
}
执行代码就变成下面这样:
f1(f2);
采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。
事件监听
另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
还是以f1和f2为例。首先,为f1绑定一个事件(这里采用的jQuery的写法)。
f1.on('done', f2);
上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:
function f1(){
setTimeout(function () {
// f1的任务代码
f1.trigger('done');
}, 1000);
}
f1.trigger(‘done’)表示,执行完成后,立即触发done事件,从而开始执行f2。
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
发布/订阅
上一节的"事件",完全可以理解成"信号"。
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。
首先,f2向"信号中心"jQuery订阅"done"信号。
jQuery.subscribe("done", f2);
然后,f1进行如下改写:
function f1(){
setTimeout(function () {
// f1的任务代码
jQuery.publish("done");
}, 1000);
}
jQuery.publish(“done”)的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。
此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe("done", f2);
这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
Promise基本使用
Promise是js中进行异步编程的新的解决方案,Promise这个英文其中文含义是承诺、保证的意思,也就代表着未来要干的事情。
Promise是一个构造函数用来创建Promise的对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象创建
Promise对象的创建有很多方法,但主要有如下三种方法,有必要先了解对象是怎么创建的,它有些什么内容,这对后面的学习是很有帮助的
- 直接实例化
- Promise.resolve
- Promise.reject
new
Promise 是一个对象,它包含一个状态 PromiseStatus 和一个值 PromiseValue。在下面的例子中,你可以看到 PromiseStatus 的值是 pending, PromiseValue 的值是 undefined。不过 - 你将永远不会与这个对象进行交互,你甚至不能访问 PromiseStatus 和 PromiseValue 这两个属性!然而,在使用 Promise 的时候,这俩个属性的值是非常重要的。

Promise.resolve

Promise.reject

promise对象的状态(生命周期)
PromiseStatus 的值,也就是 Promise 的状态,可以是以下三个值之一:
- ✅
fulfilled:promise已经被resolved。一切都很好,在promise内部没有错误发生。 - ❌
rejected:promise已经被rejected。哎呦,某些事情出错了。 - ⏳
pending:promise暂时还没有被解决也没有被拒绝,仍然处于pending状态
只有下面两种状态改变形式,并且只能改变一次
-
pending->resolved
-
peding->rejected
上面的3种创建Promise的方法都是直接创建某个固定状态的Promise对象,如何创建初始为某种状态,之后变为其它状态呢?状态转换有多少种可能呢?状态又是如何转变呢?转变之后会发生什么呢?这都是我们要回答的问题。先来回答第一个问题,状态的转换形式

执行器
实例化的方式创建Promise对象时,给构造函数传递的回调函数有一个专门的称谓:执行器,它就是用来执行异步的任务,异步任务执行完毕之后会进行Promise状态转换处理。这个回调函数实际上接受两个参数。
- 第一个参数的值经常被叫做
resolve或res,它是一个函数,在Promise应该解决resolve的时候会被调用。 - 第二个参数的值经常被叫做
reject或rej,它也是一个函数,在Promise出现一些错误应该被拒绝reject的时候被调用。

让我们尝试看看当我们调用 resolve 或 reject 方法时得到的日志。在我的例子中,把 resolve 方法叫做 res,把 reject 方法叫做 rej。

从中可以看到实例化Promise时执行器是会理解得到调用执行的,也就是说它是***同步***执行的,并且
- 当调用执行器的第一个函数参数
res时,会把Promise对象由pending变为fulfilled状态 - 当调用执行器的第二个函数参数
rej时,会把Promise对象由pending变为rejected状态
知道了如何转变状态,那么转变状态之后要做什么呢?这就要靠Promise对象提供的以下三个方法去处理了
- then:在一个 promise 被 resolved 后调用
- catch:在一个 promise 被 rejected 后被调用
- finally:不论 promise 是被 resolved 还是 reject 总是调用
then
假定有下面这样的方法,如果图片被加载完成并且一切正常,让我们用加载完的图片解决 (resolve)promise。否则,如果在加载文件时某个地方有一个错误,我们将会用发生的错误拒绝 (reject)promise 。

接下来我们就像下面这样来调用此异步方法:

如果下载图片一切正常,getImage方法内就会调用resolve方法,那么就会让Promise对象从pending状态变为fullfilled状态,那么就会调用then方法指定的回调函数,并且把调用resolve时传递的参数传给这个then的回调函数

如果失败的话,就会调用catch指定的方法,相应的也会把参数传递给catch中的回调函数

then的2个参数的形式
除了像前面那样分别用then与catch指定成功的回调与失败的回调外,也可以只用then方法同时指定成功的回调与失败的回调
getImage(file).then(res=>console.log(res),err=>console.log(err))
then的第一个参数是成功时的回调,第二个是失败时的回调.需要注意的是***成功回调函数如果执行的过程中出现错误,成功失败的回调函数不会捕获这个错误***。
延迟指定then的回调
异步任务完成之后才通过then添加成功与失败的回调函数,这些函数仍然能执行。
let promise = Promise.resolve("ok")//创建出来时已经是Resolved状态
//再指定then的成功回调,仍然可以执行回调
promise.then((data) => {
console.log("promise then:", data)
})
下面的promise最开始是pending状态,之后再变为Resolved状态
let promise = new Promise((res, rej) => {
setTimeout(() => {
res("ok")
}, 1000)
})
setTimeout(() => {
promise.then((data) => {
console.log("promise then:", data)
})
}, 2)
失败状态以及失败回调也与成功一样的性质
catch
catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同,如果Promise的状态是resolved,那么catch指定的回调函数永远不会得到调用
执行器抛出一个错误,大多数时候将调用catch方法
var p1 = new Promise(function(resolve, reject) {
throw 'Uh-oh!';
});
p1.catch(function(e) {
console.log(e); // "Uh-oh!"
});
在异步函数中抛出的错误不会被catch捕获到
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // 不会执行
});
在resolve()后面抛出的错误会被忽略
var p3 = new Promise(function(resolve, reject) {
resolve();
throw 'Silenced Exception!';
});
p3.catch(function(e) {
console.log(e); // 不会执行
});
finally
**finally()** 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在then()和catch()中各写一次的情况。
finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:
- 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。
- 由于无法知道
promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。 - 与
Promise.resolve(2).then(() => {}, () => {})(resolved的结果为undefined)不同,Promise.resolve(2).finally(() => {})resolved的结果为2。 - 同样,
Promise.reject(3).then(() => {}, () => {})(resolved 的结果为undefined),Promise.reject(3).finally(() => {})rejected 的结果为3。
let promise = new Promise((res, rej) => {
setTimeout(() => {
res("ok")
}, 1000)
})
promise
.then((res) => {
console.log("result:", res)
})
.catch((err) => {
console.log("error:", err)
})
.finally(() => {
console.log("finally--")
})
Promise执行原理
了解了Promise的基本使用之后,你或许有个疑惑,Promise不是异步编程模式的一种吗?异步性体现在哪里?先看看下面的图示

Promise异步性
从上图中,我们可以看出then指定的方法执行的异步性,不仅仅是then,catch、finally指定的方法其执行都是异步的。
而且我们知道通过setTimeout也可以创建一些异步任务,那么这两种方式有没有什么不同呢?执行有没有先后顺序呢?了解这个之前必须要先了解宏队列与微队列
宏队列与微队列
在事件循环内部,实际上有 2 种类型的队列:宏任务(macro)队列 (或者只是叫做 任务队列 )和 微任务队列。
(宏)任务队列用于 宏任务,微任务队列用于 微任务。下表列出了一些最常见的宏任务与微任务。
| (Macro)task | setTimeout | setInterval | setImmediate| ajax回调 | DOM事件回调 |
| ----------- | ------------------------------------------------------------ |
| Microtask | process.nextTick | Promise callback | queueMicrotask | MutationObserver的回调 |
我们看到 Promise 在微任务列表中! 当一个 Promise 解决 (resolve) 并且调用它的 then()、catch() 或 finally() 方法的时候,这些方法里的回调函数被添加到微任务队列!
这意味着 then(),chatch() 或 finally() 方法内的回调函数不是立即被执行,本质上是为我们的 JavaScript 代码添加了一些异步行为!
那么什么时候执行 then(),catch(),或 finally() 内的回调呢?
事件循环给与任务不同的优先级:
- 当前在调用栈 (call stack) 内的所有函数会被执行。当它们返回值的时候,会被从栈内弹出。
- 当调用栈是空的时,所有排队的微任务会一个接一个从微任务任务队列中弹出进入调用栈中,然后在调用栈中被执行!(微任务自己也能返回一个新的微任务,有效地创建无限的微任务循环 )
- 如果调用栈和微任务队列都是空的,事件循环会检查宏任务队列里是否还有任务。如果宏任务中还有任务,会从宏任务队列中弹出进入调用栈,被执行后会从调用栈中弹出!
让我们快速地看一个简单的例子:
- Task1: 立即被添加到调用栈中的函数,比如在我们的代码中立即调用它。
- Task2,Task3,Task4: 微任务,比如
promise中then方法里的回调,或者用queueMicrotask添加的一个任务。 - Task5,Task6: 宏任务,比如
setTimeout 或者 setImmediate里的回调

假定有下面的代码,在这段代码中,我们有宏任务 setTimeout 和 微任务 promise 的 then 回调。一旦 JavaScript 引擎到达 setTimeout 函数所在的那行就会涉及到事件循环。让我们一步一步地运行这段代码,看看会得到什么样的日志!

在第一行,JavaScript 引擎遇到了 console.log() 方法,它被添加到调用栈,之后它在控制台输出值 Start!。console.log 函数从调用栈内弹出,之后 JavaScript 引擎继续执行代码。

JavaScript 引擎遇到了 setTimeout 方法,他被弹入调用栈中。setTimeout 是浏览器的原生方法:它的回调函数 (() => console.log('In timeout')) 将会被添加到 Web API,直到计时器完成计时。尽管我们为计时器提供的值是 0,在它被添加到宏任务队列 (setTimeout 是一个宏任务) 之后回调还是会被首先推入 Web API。

JavaScript 引擎遇到了 Promise.resolve 方法。Promise.resolve 被添加到调用栈。在 Promise 解决 (resolve) 值之后,它的 then 中的回调函数被添加到微任务队列。

Javascript引擎遇到了console.log()方法,它会被理解添加到调用栈里,并且会输出End!到控制台,并且之后出栈

JavaScript 引擎看到调用栈现在是空的。由于调用栈是空的,它将会去检查在微任务队列中是否有在排队的任务!是的,有任务在排队,promise 的 then 中的回调函数正在等待轮到它!它被弹入调用栈,之后它输出了 promise 被解决后( resolved )的值: 在这个例子中的字符串 Promise!。

JavaScript 引擎看到调用栈是空的,因此,如果任务在排队的话,它将会再次去检查微任务队列。此时,微任务队列完全是空的。
到了去检查宏任务队列的时候了:setTimeout 回调仍然在那里等待!setTimeout 被弹入调用栈。回调函数返回 console.log 方法,输出了字符串 In timeout!。setTimeout 回调从调用栈中弹出。

下面是一段更复杂的例子,思考一下它的输出会是怎么样的?
setTimeout(() => {
console.log('setTimeout1')
Promise.resolve(3).then(value => {
console.log('onResolved3', value)
})
})
setTimeout(() => {
console.log('setTimeout2')
})
Promise.resolve(1).then(value => {
console.log('onResolved1', value)
})
Promise.resolve(2).then(value => {
console.log('onResolved2', value)
})
结果是:
//浏览器执行结果
/*
onResolved1 1
onResolved2 2
setTimeout1
onResolved3 3
setTimeout2*/
//node上执行结果
/*
onResolved1 1
onResolved2 2
setTimeout1
setTimeout2
onResolved3 3*/
Promise的链式调用
连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。
回调地狱与解决
在过去,要想做多重的异步操作,会导致经典的回调地狱:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
doSomething、doSomethingElse、doThirdThing这样的方法,大致都是如下的写法
function doSomething(){ return new Promise((resolve,reject)=>{ try { //做异步任务。。。。。 resolve(xx) }catch(e){ reject(e) } }) }
现在,我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
then 里的参数是可选的,catch(failureCallback) 是 then(null, failureCallback) 的缩略形式。如下所示,我们也可以用箭头函数来表示:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {console.log(`Got the final result: ${finalResult}`)})
.catch(failureCallback);
then的返回值
then的返回值依据以下规则返回。如果 then 中的回调函数:
- 返回了一个值,那么
then返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。 - 没有返回任何值,那么
then返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为undefined。 - 抛出一个错误,那么
then返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。 - 返回一个已经是接受状态的 Promise,那么
then返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。 - 返回一个已经是拒绝状态的 Promise,那么
then返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。 - 返回一个未定状态(
pending)的 Promise,那么then返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
下面是常见的使用示例
let promise = new Promise((res, rej) => {
setTimeout(() => {
res("ok")
}, 10)
})
promise
.then((res) => {
console.log("result:", res)
})
.then((res) => {
console.log("result2:", res)
return 5
})
.then((res) => {
console.log("result3:", res)
})
最终的输出结果是:
result: ok
result2: undefined
result3: 5
catch后的链式调用
有可能会在一个回调失败之后继续使用链式操作,即,使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用。请阅读下面的例子:
有可能会在一个回调失败之后继续使用链式操作,即,使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用。请阅读下面的例子:
new Promise((resolve, reject) => {
console.log('初始化');
resolve();
})
.then(() => {
throw new Error('有哪里不对了');
console.log('执行「这个」”');
})
.catch(() => {
console.log('执行「那个」');
})
.then(() => {
console.log('执行「这个」,无论前面发生了什么');
});
输出结果如下:
初始化
执行“那个”
执行“这个”,无论前面发生了什么
**注意:**因为抛出了错误 有哪里不对了,所以前一个 执行「这个」 没有被输出。
由于catch方法内部的实现就是调用Promise.prototype.then(undefined, onRejected),所以其返回值规则与then的返回值规则是类似的。
Internally calls
Promise.prototype.thenon the object upon which is called, passing the parametersundefinedand theonRejectedhandler received; then returns the value of that call (which is aPromise).
Promise的错误处理
Promise的错误处理主要是靠Promise对象的catch与then中第二个回调函数来处理,以及靠几个全局的事件来进行全局的错误处理。这里需要说明的是,这里说的错误处理包含2种情况
- 抛出异常这种情况
- Promise对象是或者变为Rejected状态引发的回调函数调用
catch的基本使用已经在前面讲过,这里阐述一些更细节的东西,比如
- then的错误处理与catch的差异
- 链式调用的错误处理
- 全局错误的事件处理方法
catch与then的错误处理
当Promise对象是Rejected状态时或者执行器直接抛出错误时会执行catch指定的函数或then指定的第二个回调函数,两者的区别是then的第一个回调函数抛出的错误,catch仍然可以捕获处理
比如下面的代码最终会捕获then中的错误
let promise = new Promise((res, rej) => {
setTimeout(() => {
res("ok")
// rej("--an error--")
}, 10)
})
promise
.then((res) => {
console.log("result:", res)
throw new Error("error in then ")
})
.catch((e) => {
console.log("error:", e)
})
而下面的代码最终是捕获不了then中抛出的错误的
let promise = new Promise((res, rej) => {
setTimeout(() => {
res("ok")
// rej("--an error--")
}, 10)
})
promise.then(
(res) => {
console.log("result:", res)
throw new Error("error in then ")
},
(e) => {
console.log("error:", e)
}
)
链式调用的错误传递
在之前的回调地狱示例中,你可能记得有 3 次 failureCallback 的调用,而在 Promise 链中只有尾部的一次调用。
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
通常,一遇到异常抛出,浏览器就会顺着 Promise 链寻找下一个 onRejected 失败回调函数或者由 .catch() 指定的回调函数。这和以下同步代码的工作原理(执行过程)非常相似。
try {
let result = syncDoSomething();
let newResult = syncDoSomethingElse(result);
let finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
通过捕获所有的错误,甚至抛出异常和程序错误,Promise 解决了回调地狱的基本缺陷。这对于构建异步操作的基础功能而言是很有必要的。
全局错误处理
当 Promise 被拒绝时,会有下文所述的两个事件之一被派发到全局作用域(通常而言,就是window;如果是在 web worker 中使用的话,就是 Worker 或者其他 worker-based 接口)。这两个事件如下所示:
-
当 Promise 被拒绝、并且在
reject函数处理该 rejection 之后会派发此事件。 -
当 Promise 被拒绝,但没有提供
reject函数来处理该 rejection 时,会派发此事件。
以上两种情况中,PromiseRejectionEvent 事件都有两个属性,一个是 promise 属性,该属性指向被驳回的 Promise,另一个是 reason 属性,该属性用来说明 Promise 被驳回的原因。
因此我们可以通过这些事件给Promise提供全局的失败处理,这样就可以把所有的错误处理放置在同一个地方。
window.addEventListener(
"unhandledrejection",
(event) => {
/* 你可以在这里添加一些代码,以便检查
event.promise 中的 promise 和
event.reason 中的 rejection 原因 */
event.preventDefault()
console.log("全局错误处理")
},
false
)
let promise = new Promise((res, rej) => {
setTimeout(() => {
//res("ok")
rej("--an error--")
}, 10)
})
调用 event 的 preventDefault() 方法是为了告诉 JavaScript 引擎当 Promise 被拒绝时不要执行默认操作,默认操作一般会包含把错误打印到控制台,Node 就是如此的。
Promise的组合使用
Promise.all() 和 Promise.race() 是并行运行异步操作的两个组合式工具。
Promise.all
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。
它通常在启动多个异步任务并发运行并为其结果创建承诺之后使用,以便人们可以等待所有任务完成。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
非Promise参数
如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中(如果 promise 完成的话):
// this will be counted as if the iterable passed is empty, so it gets fulfilled
var p = Promise.all([1,2,3]);
// this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled
var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
// this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected
var p3 = Promise.all([1,2,3, Promise.reject(555)]);
// using setTimeout we can execute code after the stack is empty
setTimeout(function(){
console.log(p);
console.log(p2);
console.log(p3);
});
// logs
// Promise { <state>: "fulfilled", <value>: Array[3] }
// Promise { <state>: "fulfilled", <value>: Array[4] }
// Promise { <state>: "rejected", <reason>: 555 }
异步与同步
下面的例子中演示了 Promise.all 的异步性(如果传入的可迭代对象是空的,就是同步):
// we are passing as argument an array of promises that are already resolved,
// to trigger Promise.all as soon as possible
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];
var p = Promise.all(resolvedPromisesArray);
// immediately logging the value of p
console.log(p);
// using setTimeout we can execute code after the stack is empty
setTimeout(function(){
console.log('the stack is now empty');
console.log(p);
});
// logs, in order:
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }
如果 Promise.all 失败,也是一样的:
var mixedPromisesArray = [Promise.resolve(33), Promise.reject(44)];
var p = Promise.all(mixedPromisesArray);
console.log(p);
setTimeout(function(){
console.log('the stack is now empty');
console.log(p);
});
// logs
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "rejected", <reason>: 44 }
但是,Promise.all 当且仅当传入的可迭代对象为空时为同步:
var p = Promise.all([]); // will be immediately resolved
var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
console.log(p);
console.log(p2)
setTimeout(function(){
console.log('the stack is now empty');
console.log(p2);
});
// logs
// Promise { <state>: "fulfilled", <value>: Array[0] }
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }
快速返回失败
Promise.all 在任意一个传入的 promise 失败时返回失败。例如,如果你传入的 promise中,有四个 promise 在一定的时间之后调用成功函数,有一个立即调用失败函数,那么 Promise.all 将立即变为失败。
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
reject('reject');
});
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
//From console:
//"reject"
//You can also use .catch
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}).catch(reason => {
console.log(reason)
});
//From console:
//"reject"
Promise.race
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会是解决或拒绝状态的。
race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
如果传的迭代是空的,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则Promise.race 将解析为迭代中找到的第一个值。
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// 两个都完成,但 p2 更快
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "four");
});
Promise.race([p3, p4]).then(function(value) {
console.log(value); // "three"
// p3 更快,所以它完成了
}, function(reason) {
// 未被调用
});
var p5 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "five");
});
var p6 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "six");
});
Promise.race([p5, p6]).then(function(value) {
// 未被调用
}, function(reason) {
console.log(reason); // "six"
// p6 更快,所以它失败了
});
Promise的实现
prototype的实现
// 自定义Promise
// ES5匿名函数自调用实现模块化
;(function (window) {
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
/***************Promise构造型函数 start********************** */
function Promise(executor) {
// 只要产生了新的Promise对象,这些参数都是会重新创建的.
//下面注释中的关于Promise的描述不是特别的准确,只考虑连续有2个then的情况即可
const that = this
// 三个属性
that.status = PENDING //Promise对象状态属性,初始状态为 pending
that.data = "undefined" // 用于存储结果数据
that.callbacks = [] //保存待执行的回调函数 ,数据结构:{onResolved(),onRejected()}
function resolve(value) {
// RESOLVED 状态只能改变一次
if (that.status !== PENDING) {
return
}
that.status = RESOLVED
that.data = value
if (that.callbacks.length > 0) {
setTimeout(() => {
that.callbacks.forEach((callbackObj) => {
callbackObj.onResolved(value)
})
})
}
}
function reject(seaon) {
if (that.status !== PENDING) {
return
}
that.status = REJECTED
that.data = seaon
if (that.callbacks.length > 0) {
setTimeout(() => {
that.callbacks.forEach((callbackObj) => {
callbackObj.onRejected(seaon)
})
})
}
}
try {
//执行器函数立即执行
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
/***************Promise构造型函数 end********************** */
/**
* then的功能呢有:
* 1.添加成功,失败的回调函数
* 2.返回一个全新的promise对象
* @param {*} onResolved
* @param {*} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
onResolved = typeof onResolved === "function" ? onResolved : (value) => value // 向后传递成功的value
// 指定默认的失败的回调(实现错误/异常穿透的关键点)
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason
}
//这个that指代的是最开始创建的promise对象,就是html中最开始实例化Promise时产生的对象
const that = this
return new Promise((resolve, reject) => {
function handle(callback) {
// 调用成功的回调函数 onResolved
//1.如果抛出异常,return的promise就 会失败,reason就 是error
//2.如果回调函数返回不是promise, return的promise就 会成功,value就是返回的值
//3.如果回调函数返回是promise, return的promise结 果就是这个promise的结果
try {
// console.log("--", callback)
// callback代表的就是第一个Promise对象的then方法的参数
const result = callback(that.data)
//console.log("----", result)
if (result instanceof Promise) {
//第三种情况
result.then(
(value) => resolve(value),
(reason) => reject(reason)
)
} else {
//这里的resolve方法就是此新的Promise对象的参数,会导致此新的Promise自己的then方法的回调函数得到执行
resolve(result) //第二种情况
}
} catch (e) {
reject(e) //第一种情况,会导致异常继续往后抛,产生异常穿透的效果
}
}
// 异步任务还没有执行完毕,就已经添加了异步处理函数,只会创建回调对象并添加到数组中,这个if分支处理这种情况
if (that.status === PENDING) {
that.callbacks.push({
//调用此push的对象的onResolved方法--->
// ->调用handle方法 --> 在handle内调用这里的参数方法也就是onResolved方法->也就是then方法的第一个参数
onResolved() {
handle(onResolved) //onResolved参数来自于then的第一个形参
},
onRejected() {
handle(onRejected) //onRejected参数来自于then的第二个形参
},
})
} else if (that.status === RESOLVED) {
setTimeout(() => {
handle(onResolved)
})
} else {
setTimeout(() => {
//调用失败的回调函数 onRejected
handle(onRejected)
})
}
})
}
//Promise原型对象 catch ,参数为失败的回掉函数 onRejected
//返回一个新的Promise对象
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
// Promise函数对象的 resolve 方法
//返回一个新的Promise对象,Promise.resolve()中可以传入Promise
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
}
// Promise函数对象的 reject 方法
//返回一个新的Promise对象 Promise.reject中不能再传入Promise
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
// Promise函数对象的 all 方法,接受一个promise类型的数组
// 返回一个新的Promise对象
Promise.all = function (promises) {
// 保证返回的值得结果的顺序和传进来的时候一致
// 只有全部都成功长才返回成功
const values = new Array(promises.length) // 指定数组的初始长度
let successCount = 0
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
// 由于p有可能不是一个Promise
Promise.resolve(p).then(
(value) => {
successCount++
values[index] = value
if (successCount === promises.length) {
resolve(values)
}
},
// 如果失败
(reason) => {
reject(reason)
}
)
})
})
}
// Promise函数对象的 race 方法,接受一个promise类型的数组
// 返回一个新的Promise对象
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((p) => {
Promise.resolve(p).then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
}
)
})
})
}
// 把Promise暴露出去
window.Promise = Promise
})(window)
class实现
// 自定义Promise
// ES5匿名函数自调用实现模块化
;(function (window) {
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
class Promise {
// 参数为executor函数
constructor(executor) {
const that = this
// 三个属性
that.status = PENDING //Promise对象状态属性,初始状态为 pending
that.data = "undefined" // 用于存储结果数据
that.callbacks = [] //保存待执行的回调函数 ,数据结构:{onResolved(){},onRejected(){}}
function resolve(value) {
// RESOLVED 状态只能改变一次
if (that.status !== PENDING) {
return
}
that.status = RESOLVED
that.data = value
//执行异步回调函数 onResolved
if (that.callbacks.length > 0) {
setTimeout(() => {
// 放入队列中执行所有成功的回调
that.callbacks.forEach((callbackObj) => {
callbackObj.onResolved(value)
})
})
}
}
function reject(seaon) {
if (that.status !== PENDING) {
return
}
that.status = REJECTED
that.data = seaon
//执行异步回调函数 onRejected
if (that.callbacks.length > 0) {
setTimeout(() => {
// 放入队列中执行所有失败的回调
that.callbacks.forEach((callbackObj) => {
callbackObj.onRejected(seaon)
})
})
}
}
try {
//执行器函数立即执行
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
//Promise原型对象 then ,两个回掉函数 成功 onResolved ,失败onRejected
//返回一个新的Promise对象
then(onResolved, onRejected) {
onResolved = typeof onResolved === "function" ? onResolved : (value) => value // 向后传递成功的value
// 指定默认的失败的回调(实现错误/异常传透的关键点)
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason
} // 抽后传递失败的reason
const that = this
return new Promise((resolve, reject) => {
//调用指定回调函数处理, 根据执行结果, 改变return的promise的状态
function handle(callback) {
// 调用成功的回调函数 onResolved
//1.如果抛出异常,return的promise就 会失败,reason就 是error
//2.如果回调函数返回不是promise, return的promise就 会成功,value就是返回的值
//3.如果回调函数返回是promise, return的promise结 果就是这个promise的结果
try {
const result = callback(that.data)
if (result instanceof Promise) {
result.then(
(value) => resolve(value),
(reason) => reject(reason)
)
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
// 当前状态还是pending状态, 将回调函数保存起来
if (that.status === PENDING) {
that.callbacks.push({
onResolved(value) {
handle(onResolved)
},
onRejected(reason) {
handle(onRejected)
},
})
} else if (that.status === RESOLVED) {
setTimeout(() => {
handle(onResolved)
})
} else {
setTimeout(() => {
//调用失败的回调函数 onRejected
handle(onRejected)
})
}
})
}
//Promise原型对象 catch ,参数为失败的回掉函数 onRejected
//返回一个新的Promise对象
catch(onRejected) {
return this.then(undefined, onRejected)
}
// Promise函数对象的 resolve 方法
//返回一个新的Promise对象,Promise.resolve()中可以传入Promise
static resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
}
// Promise函数对象的 reject 方法
//返回一个新的Promise对象 Promise.reject中不能再传入Promise
static reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
// Promise函数对象的 all 方法,接受一个promise类型的数组
// 返回一个新的Promise对象
static all = function (promises) {
// 保证返回的值得结果的顺序和传进来的时候一致
// 只有全部都成功长才返回成功
const values = new Array(promises.length) // 指定数组的初始长度
let successCount = 0
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
// 由于p有可能不是一个Promise
Promise.resolve(p).then(
(value) => {
successCount++
values[index] = value
if (successCount === promises.length) {
resolve(values)
}
},
// 如果失败
(reason) => {
reject(reason)
}
)
})
})
}
// Promise函数对象的 race 方法,接受一个promise类型的数组
// 返回一个新的Promise对象
static race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((p) => {
Promise.resolve(p).then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
}
)
})
})
}
}
// 把Promise暴露出去
window.Promise = Promise
})(window)
自定义promise实现的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- <script src="./lib/promise_class.js"></script> -->
<script src="./lib/promise_prototype.js"></script>
<script>
/* const p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(10)
console.log('resolve改变状态之后') // 用来证明resolve(1)函数异步执行
})
});
p.then(value => {
console.log('value1', value)
}, reason => {
console.log('reason1', reason)
})
p.then(value => {
console.log('value2', value)
}, reason => {
console.log('reason2', reason)
}) */
</script>
<script>
const p = new Promise((resolve, reject) => {
//resolve(100)
//throw new Error("err:----")
// 如果在异步任务中出现了异常, promise的cathe就捕获不到这个异常
setTimeout(() => {
throw new Error("err in async task:----")
})
})
p.then((value) => {
//throw new Error("err2:----")
// console.log('value1', value)
}).catch((reason) => {
console.log("reason:", reason)
})
</script>
<!--<script>
new Promise((resolve, reject) => {
setTimeout(() => {
//resolve(1)
reject(2)
})
}).then(value => {
console.log('value1', value)
}, reason => {
console.log('reason1', reason)
//return 3
return new Promise((resolve, reject) => reject(5))
}).then(value => {
console.log('value2', value)
}, reason => {
console.log('reason2', reason)
throw 6
}).catch(e => {
console.log('reason3', e)
return new Promise((resolve, reject) => { // 中断promise链
})
}).then(value => {
console.log('value3', value)
}, reason => {
console.log('reason4', reason)
})
</script>-->
<script>
/* let p1 = Promise.resolve(1)
let p2 = Promise.resolve(Promise.resolve(2))
let p3 = Promise.reject(3)
let p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(4)
}, 1000)
})
*/
/* p1.then(value => {
console.log('p1', value)
})
p2.then(value => { // 执行 onfulfilled()回调函数,官方名字
console.log('p2', value)
})
p3.catch(error => { // 执行 onfulfilled()回调函数,官方名字
console.log('p3', error)
})*/
/* let all = Promise.all([p4, 9, p1, p2])
all.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
) */
/* let race = Promise.race([p4, 9,p1, p2]);
race.then(
value => {
console.log(value)
},
reason => {
console.log(reason)
})*/
</script>
</body>
</html>
Generator
生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。下面的gen是生成器函数,g是生成器对象
function* gen() {
yield 1;
yield 2;
yield 3;
}
let g = gen();
Iterable(可迭代协议)
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#iterable
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object))。
要成为可迭代对象, 一个对象必须实现 **@@iterator** 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:
| 属性 | 值 |
|---|---|
[Symbol.iterator] | 一个无参数的函数,其返回值为一个符合迭代器协议的对象。 |
此函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。 在此生成器函数的内部,可以使用yield提供每个条目。
let someString = "hi";
typeof someString[Symbol.iterator];
Iterator(迭代器协议)
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
只有实现了一个拥有以下语义(semantic)的 **next()** 方法,一个对象才能成为迭代器:
| 属性 | 值 |
|---|---|
next | 一个无参数函数,返回一个应当拥有以下两个属性的对象:done(boolean)如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。value迭代器返回的任何 JavaScript 值。done 为 true 时可省略。next() 方法必须返回一个对象,该对象应当有两个属性: done 和 value,如果返回了一个非对象值(比如 false 或 undefined),则会抛出一个 TypeError 异常("iterator.next() returned a non-object value")。 |
**备注:**不可能判断一个特定的对象是否实现了迭代器协议,然而,创造一个同时满足迭代器协议和可迭代协议的对象是很容易的(如下面的示例中所示)。
这样做允许一个迭代器能被各种需要可迭代对象的语法所使用。因此,很少会只实现迭代器协议,而不实现可迭代协议。
var myIterator = { next: function() { // ... }, [Symbol.iterator]: function() { return this } }
下面的代码演示了迭代器的使用
let someString = "hi";
let iterator = someString[Symbol.iterator]();
iterator + ""; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
一些内置的语法结构——比如展开语法——其内部实现也使用了同样的迭代协议:
[...someString] // ["h", "i"]
自定义迭代器
我们可以通过提供自己的 @@iterator 方法,重新定义迭代行为:
// 必须构造 String 对象以避免字符串字面量 auto-boxing
var someString = new String("hi");
someString[Symbol.iterator] = function() {
return { // 只返回一次元素,字符串 "bye",的迭代器对象
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
注意重新定义的 @@iterator 方法是如何影响内置语法结构的行为的:
[...someString]; // ["bye"]
someString + ""; // "hi"
下面是另一个简单的迭代器实现
function makeIterator(array) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < array.length ? {
value: array[nextIndex++],
done: false
} : {
done: true
};
}
};
}
let it = makeIterator(['哟', '呀']);
console.log(it.next().value); // '哟'
console.log(it.next().value); // '呀'
console.log(it.next().done); // true
类中创建的一个迭代器
class SimpleClass {
constructor(data) {
this.data = data
}
[Symbol.iterator]() {
// Use a new index for each iterator. This makes multiple
// iterations over the iterable safe for non-trivial cases,
// such as use of break or nested looping over the same iterable.
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return {value: this.data[index++], done: false}
} else {
return {done: true}
}
}
}
}
}
const simple = new SimpleClass([1,2,3,4,5])
for (const val of simple) {
console.log(val) //'1' '2' '3' '4' '5'
}
生成器基础
生成器对象既是迭代器,也是可迭代对象:
let aGeneratorObject = function* (){
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
// 返回"function", 因为有一个next方法,所以这是一个迭代器
typeof aGeneratorObject[Symbol.iterator];
// 返回"function", 因为有一个@@iterator方法,所以这是一个可迭代对象
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// 返回true, 因为@@iterator方法返回自身(即迭代器),所以这是一个格式良好的可迭代对象
[...aGeneratorObject];
// 返回[1, 2, 3]
console.log(Symbol.iterator in aGeneratorObject)
// 返回true, 因为@@iterator方法是aGeneratorObject的一个属性
yield
yield 关键字用来暂停和恢复一个生成器函数((function* 或遗留的生成器函数)。
yield关键字使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字。
yield关键字实际返回一个IteratorResult对象,它有两个属性,value和done。value属性是对yield表达式求值的结果,而done是false,表示生成器函数尚未完全完成。
一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法被调用。每次调用生成器的next()方法时,生成器都会恢复执行,直到达到以下某个值:
yield,导致生成器再次暂停并返回生成器的新值。 下一次调用next()时,在yield之后紧接着的语句继续执行。throw用于从生成器中抛出异常。这让生成器完全停止执行,并在调用者中继续执行,正如通常情况下抛出异常一样。- 到达生成器函数的结尾;在这种情况下,生成器的执行结束,并且
IteratorResult给调用者返回undefined并且done为true。 - 到达
return语句。在这种情况下,生成器的执行结束,并将IteratorResult返回给调用者,其值是由return语句指定的,并且done为true。
next的参数
如果将参数传递给生成器的next()方法,则该值将成为生成器当前yield操作返回的值。
function* generator() {
let s1 = yield step1()
console.log("yield return ", s1)
let s2 = yield step2()
}
let it = generator()
function step1() {
console.log("11")
}
function step2() {
console.log("22")
}
it.next(100) //开始执行step1函数,执行完毕后暂停generator的执行,这里的参数100是没有意义的
it.next(200) //从s1处恢复,并把200作为yield step1这一步的结果赋值给s1变量
输出的结果是:
11
yield return 200
22
异步中的运用
function* generator() {
let s1 = yield step1()
// console.log("yield return ", s1)
let s2 = yield step2(s1)
let s3 = yield step3(s2)
console.log("s3 步骤3的结果:", s3)
}
let it = generator()
it.next()
function step1() {
setTimeout(() => {
console.log("11")
it.next(11)
}, 10)
}
function step2(step1Result) {
setTimeout(() => {
console.log("22", step1Result)
it.next(22)
}, 10)
}
function step3(step2Result) {
setTimeout(() => {
console.log("33", step2Result)
it.next(33)
}, 10)
}
输出的结果是
11
22 11
33 22
s3 步骤3的结果: 33
上面代码执行的流程如下
- 先定义了生成器函数与3个step函数
- 创建迭代器it
- 调用it.next开始执行generator函数
- 开始执行step1,setTimeout的回调函数放入宏队列,step1执行完毕
- generator函数此时暂停执行
- 10毫秒后,step1的setTimeout的回调函数开始执行
- 输出日志11
- 调用next把第一步的结果11传递给next
- generator恢复执行,第一行代码
yield step1的返回值是刚刚next传递的11,s1的结果就是11 - 开始执行step2函数,第一步的结果11传递给了此函数。
- 最终
s1=11,s2=22,s3=33
async与await
async functions 和 await 关键字是最近添加到JavaScript语言里面的。它们是ECMAScript 2017 JavaScript版的一部分(参见ECMAScript Next support in Mozilla)。简单来说,它们是基基于promises的语法糖,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码,因此它们非常值得学习。
async函数
首先,我们使用 async 关键字,把它放在函数声明之前,使其成为 async function。异步函数是一个知道怎样使用 await 关键字调用异步代码的函数。
尝试在浏览器的JS控制台中键入以下行:
function hello() { return "Hello" };
hello();
该函数返回“Hello” —— 没什么特别的,对吧?
如果我们将其变成异步函数呢?请尝试以下方法:
async function hello() { return "Hello" };
hello();
哈。现在调用该函数会返回一个 promise。这是异步函数的特征之一 —— 它保证函数的返回值为 promise。
你也可以创建一个异步函数表达式(参见 async function expression ),如下所示:
let hello = async function() { return "Hello" };
hello();
你可以使用箭头函数:
let hello = async () => { return "Hello" };
这些都基本上是一样的。
要实际使用promise完成时返回的值,我们可以使用.then()块,因为它返回的是 promise:
hello().then((value) => console.log(value))
将 async 关键字加到函数申明中,可以告诉它们返回的是 promise,而不是直接返回值。此外,它避免了同步函数为支持使用 await 带来的任何潜在开销。在函数声明为 async 时,JavaScript引擎会添加必要的处理,以优化你的程序。爽!
await
当 await 关键字与异步函数一起使用时,它的真正优势就变得明显了 —— 事实上, await 只在异步函数里面才起作用。它可以放在任何异步的,基于 promise 的函数之前。它会暂停代码在该行上,直到 promise 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了。
您可以在调用任何返回Promise的函数时使用 await,包括Web API函数。
这是一个简单的示例:
async function hello() {
return greeting = await Promise.resolve("Hello");
};
hello().then(alert);
await除了可以等待Promise也可以等待非Promise,也就是说await可以等待任何值,比如:
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后会怎么处理呢?
- 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
- 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
这也就是为什么await要在异步函数的原因,因为整个函数test是异步的,此函数即使阻塞在了await testAsync这里了,也不影响test后面代码的执行
async与await的优势
先来看看不用 async/await 会怎么写
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
takeLongTime().then(v => {
console.log("got", v);
});
如果改用 async/await 呢,会是这样
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
async function test() {
const v = await takeLongTime();
console.log(v);
}
test();
眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async 结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。
又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
现在用 Promise 方式来实现这三个步骤的处理
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。
如果用 async/await 来实现呢,会是这样
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
还有更酷的,现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
这回先用 async/await 来写:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了,看着就晕!
总之:async与await的结合对于有依赖关系的异步操作处理是非常非常方便的。
错误处理
async function myFetch() {
try {
let response = await fetch('coffee.jpg');
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
} catch(e) {
console.log(e);
}
}
myFetch();
async与await使用注意点
在不是有相互依赖关系的异步操作中,你滥用await会导致性能下降,比如下面的代码
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
async function timeTest() {
await timeoutPromise(3000);
await timeoutPromise(3000);
await timeoutPromise(3000);
}
let startTime = Date.now();
timeTest().then(() => {
let finishTime = Date.now();
let timeTaken = finishTime - startTime;
alert("Time taken in milliseconds: " + timeTaken);
})
在这里,我们直接等待所有三个timeoutPromise()调用,使每个调用3秒钟。后续的每一个都被迫等到最后一个完成 - 如果你运行第一个例子,你会看到弹出框报告的总运行时间大约为9秒
而下面这种写法耗时大概3秒多一点
async function timeTest() {
const timeoutPromise1 = timeoutPromise(3000);
const timeoutPromise2 = timeoutPromise(3000);
const timeoutPromise3 = timeoutPromise(3000);
await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}
在这里,我们将三个Promise对象存储在变量中,这样可以同时启动它们关联的进程。
接下来,我们等待他们的结果 - 因为promise都在基本上同时开始处理,promise将同时完成,或者你用Promise.all也可以实现类似的效果。主要是因为这些异步操作相互之间没有依赖性,不需要一个等着一个来执行。
类的异步方法
我们可以在类/对象方法前面添加async,以使它们返回promises,并await它们内部的promises。
class Person {
async greeting() {
return await Promise.resolve(`Hi! `);
};
}
let han = new Person()
han.greeting().then(console.log);
参考资料
https://github.com/weolwo/promise-learn
https://segmentfault.com/a/1190000017877701(异步编程的几种模式)
https://segmentfault.com/a/1190000015711829 (相互依赖的异步代码演进)
https://segmentfault.com/a/1190000022743630(可视化的js,动态演示Promise async/await的过程)
https://juejin.im/post/6844903625769091079(自定义Promise的实现)
https://segmentfault.com/a/1190000007535316(关于async与await的)
JS异步编程模式
本文介绍了JavaScript中的异步编程模式,包括回调函数、事件监听、发布/订阅、Promise、Generator和async/await等。详细解释了每种模式的原理、优缺点及应用场景。
2484

被折叠的 条评论
为什么被折叠?



