1.前言
Promise
、Generator
、async/await
是Javascript
异步编程的三个解决方案。废话不多说,进入正题。
2.概念
2.1 Promise概念
Promise
是一个对象,代表了一个异步操作的最终完成(或者失败)的状态和结果值。它的状态有三个:pending
(进行中)、fulfilled
(已完成)、rejected
(已失败),状态一旦确定就不可逆。Promise
支持链式调用,使用then
方法,避免回调地狱。Promise的回调函数是异步执行,在同步代码执行完毕之后,微任务阶段进行执行。
2.2 Generator概念
Generator
是 ES6引入的特殊函数,通过function*
声明,使用yield
暂停执行和next
恢复函数执行,返回值是一个迭代器对象,通过迭代器对象控制函数的执行。
2.3 async/await概念
async/await
是 ES2017引入的函数,是Generator
和Promise
的语法糖,使得异步操作更加的方便。async
声明函数,自动将函数返回值包装成Promsie
对象。await
可以配合 async
使用,也可以单独使用(ES022为了解决模块异步的之间的加载问题)。暂停await的执行,等待 Promise的解决,并返回Promsie的值,等待的不是 Promise的值,则直接返回值。
⚠️:await
关键字后面的表达式的执行顺序先于async
函数的暂停,换句话说await 右侧表达式的同步代码全部执行完毕,才会执行暂停逻辑。之后后面的代码块包装成一个微任务,放入微任务队列中。
3.使用场景
3.1 Promise使用场景
- 很明确的成功或者失败的处理逻辑
- 多个无任何关系的异步操作(Promise.all、Promise.any、Promise.allSettled、Promise.race)
3.2 Generator使用场景
- 需要手动控制异步任务的执行
- 复杂的迭代器场景
- 状态机实现(多个状态的切换)
3.3 async/await使用场景
- 使用同步代码串行执行异步逻辑
4.优缺点
4.1 Promise优缺点
- 优点:
- 解决传统回调函数的回调地狱问题,为后面的 Generator 和 async/await 打下基础
- 状态不可逆,更安全可靠
- 具备统一的catch 方法捕捉错误
- 缺点:
- 复杂流程的链式调用代码不直观
- 状态不可逆,不能取消 Promise
- 无法中途跳出链式调用,不能在使用break、continute、return 等控制语句
4.2 Generator优缺点
- 优点:
- 对于异步任务操作更灵活,支持暂停/恢复异步流程
- 缺点:
- 代码相对复杂,需要手动驱动控制器,学习成本高
- 错误处理需要同时关注 try/catch 和外部的 throw,比较复杂
4.3 async/await优缺点
- 优点:
- 代码简洁,可读性高
- 错误处理可以使用 try/catch,与同步代码一致
- 可在异步流程中使用 break、continute、return 等控制语句
- 缺点:
- 多个无依赖关系的异步任务并行执行需要额外操作(Promsie.all)
5.代码实现
5.1 基于Generator和 Promise 实现 async/await
- 准备一个函数,参数是 generator 函数,返回值是 Promise,模拟 async
javascript
const asyncGenerator = (generatorFunc) => {
return new Promise((resolve,reject) => {
})
}
- Generator需要手动驱动迭代器执行,包装的方法的核心是自动驱动迭代器执行
javascript
const asyncGenerator = (generatorFunc) => {
return new Promise((resolve,reject) => {
// 返回一个迭代器,具备 value 和 done 属性
const generator = generatorFunc()
<!-- 用递归驱动迭代器自动执行 -->
const step = (methos,val) => {
let result = null
result = generator[methos](val)
const {value.done} = result
if(done) {
return resolve(value)
}
}
step("next")
})
}
- 将 yield 值包装成 Promise
javascript
const asyncGenerator = (generatorFunc) => {
return new Promise((resolve,reject) => {
// 返回一个迭代器,具备 value 和 done 属性
const generator = generatorFunc()
<!-- 用递归驱动迭代器自动执行 -->
const step = (methos,val) => {
let result = null
result = generator[methos](val)
const {value.done} = result
if(done) {
return resolve(value)
}
Promise.resolve(value)
}
step("next")
})
}
4.对于 Generator异常机制,Generator函数内部 没有 try/catch,使用 g.throw,则外部捕获错误,Generator函数内部 有 try/catch,使用 g.throw,则内部捕获错误。对于同步代码错误也需要异常捕获,如下
typescript
const asyncGenerator = (generatorFunc) => {
return new Promise((resolve,reject) => {
// 返回一个迭代器,具备 value 和 done 属性
const generator = generatorFunc()
<!-- 用递归驱动迭代器自动执行 -->
const step = (methos,val) => {
let result = null
try {
result = generator[methos](val)
} catch(err) {
return reject(err)
}
const {value.done} = result
if(done) {
return resolve(value)
}
Promise.resolve(value)
.then(val => step("next",val))
.catch(error => step("throw",error))
}
step("next")
})
}
- 完整代码和注释
typescript
// 实现基于Generator的async/await
const asyncGenerator = (generatorFunc) => {
// 返回一个 Promise对象,模拟 async 的返回值
return new Promise((resolve,reject) => {
// 执行Generator,返回迭代器
const generator = generatorFunc()
// 准备递归函数,自动执行迭代器
const step = (method,arg) => {
let result = null
try {
// 执行迭代器的 next 或者 throw 方法
result = generator[method](arg)
} catch(error) {
// 捕获同步代码错误,执行 reject 将错误抛出外部
return reject(error)
}
const {value,done} = result
// 若迭代器已经完成,则返回值并停止递归
if(done) {
return resolve(value)
}
// 将 yield 的结果包装成 Promise对象
Promise.resolve(value)
.then(
// 成功,则把值传递给下一代
val => step("next",val)
).catch(
// 失败则捕获错误
// Generator函数内部 没有 try/catch,使用 g.throw,则外部捕获错误
// Generator函数内部 有 try/catch,使用 g.throw多个,则内部捕获错误一个,其他外部捕获
error =>step("throw",error)
)
}
// 开始启动迭代
step("next")
})
}
5.2 Promise 实现 ajax 封装
- 创建一个函数,参数是请求选项值,返回值是一个 Promise。处理传递的参数,默认值设置
javascript
const ajaxPromise = (options) => {
return new Promise((resolve,reject) => {
if(!options || !options.url) {
reject(new Error('URL is required'));
return
}
const defaults = {
method: "GET",
data: null,
headers: {},
responseType: ''
}
const config = {...defaults,...options}
config.method = config.method.toUpperCase()
})
}
- 初始化请求并设置参数
javascript
const ajaxPromise = (options) => {
return new Promise((resolve,reject) => {
...
// 2. 初始化请求并设置参数
// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest()
// 2.1 初始化请求
let requestUrl = config.url
// GET请求需要处理 url
if(config.method === "GET" && config.data) {
const params = new URLSearchParams()
for(const key in config.data) {
params.append(key,config.data[key])
}
requestUrl = `${config.url}${config.url.includes("?")?"&":"?"}${params.toString()}`
}
xhr.open(config.method.toUpperCase(),requestUrl,true)
// 2.2 设置响应类型
if(config.responseType) {
xhr.responseType = config.responseType
}
// 2.3 设置请求头
for(const key in config.headers) {
xhr.setRequestHeader(key,config.headers[key])
}
// 2.4 设置超时
if(config.timeout) {
xhr.timeout = config.timeout
}
// 2.5 处理状态变化
xhr.onreadystatechange = () => {
// 4的状态是请求完成未成功
if(xhr.readyState !== 4) return
// 请求成功
if(xhr.status >= 200 && xhr.status < 300) {
// 响应数据进行处理
let responseData;
if(xhr.responseType === "json") {
responseData = xhr.response
} else {
try {
responseData = JSON.parse(xhr.responseText)
} catch (error) {
responseData = xhr.responseText
}
}
resolve({
data: responseData,
status: xhr.status,
statusText: xhr.statusText,
xhr: xhr
});
} else {
// 请求失败
reject({
status: xhr.status,
statusText: xhr.statusText,
response: xhr.response,
xhr: xhr,
error: new Error(`Request failed with status ${xhr.status}: ${xhr.statusText}`)
});
}
}
})
}
- 处理网络错误
javascript
const ajaxPromise = (options) => {
return new Promise((resolve,reject) => {
...
xhr.onerror = function() {
reject({
error: new Error('Network error occurred'),
xhr: xhr
});
}
})
}
- 超时处理
javascript
const ajaxPromise = (options) => {
return new Promise((resolve,reject) => {
...
let timeoutTimer;
if(config.timeout) {
timeoutTimer = setTimeout(() => {
xhr.abort()
reject({
error: new Error('Request timed out'),
xhr: xhr
});
},timeout)
}
})
}
- 发送请求
scss
const ajaxPromise = (options) => {
return new Promise((resolve,reject) => {
...
// 5.发送请求
try {
// 5.1 对于不同的请求方法,POST/PUT/PATCH,需要设置请求体
if(config.method === "POST" || config.method === "PUT" || config.method === "PATCH") {
if(!config.headers['Content-Type']) {
config.headers['Content-Type'] = "application/json"
xhr.setRequestHeader("Content-Type","application/json")
}
const contentType = config.headers['Content-Type'] || ''
let requestData = config.data
if(contentType.includes("application/json") && config.data) {
requestData = JSON.stringify(config.data)
} else if(contentType.includes("application/x-www-form-urlencoded") && config.data) {
const params = new URLSearchParams()
for(let key in config.data) {
params.append(key,config.data[key])
}
requestData = params.toString()
}
xhr.send(requestData)
} else {
// 5.2 GET请求
xhr.send()
}
} catch (error) {
// 清除超时定时器
if(timeoutTimer) {
clearTimeout(timeoutTimer)
}
reject(error)
}
})
}
- 实现快捷请求方法
kotlin
ajaxPromise.get = (url,data,options = {}) => {
return ajaxPromise({...options,url,method:"GET",data})
}
ajaxPromise.post = (url,data,options = {}) => {
return ajaxPromise({...options,url,method:"POST",data})
}
7.完整代码
scss
const ajaxPromise = (options) => {
return new Promise((resolve,reject) => {
// 1.处理传递的参数,默认值设置
if(!options || !options.url) {
reject(new Error('URL is required'));
return
}
const defaults = {
method: "GET",
data: null,
headers: {},
responseType: ''
}
const config = {...defaults,...options}
config.method = config.method.toUpperCase()
// 2. 初始化请求并设置参数
// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest()
// 2.1 初始化请求
let requestUrl = config.url
// GET请求需要处理 url
if(config.method === "GET" && config.data) {
const params = new URLSearchParams()
for(const key in config.data) {
params.append(key,config.data[key])
}
requestUrl = `${config.url}${config.url.includes("?")?"&":"?"}${params.toString()}`
}
xhr.open(config.method.toUpperCase(),requestUrl,true)
// 2.2 设置响应类型
if(config.responseType) {
xhr.responseType = config.responseType
}
// 2.3 设置请求头
for(const key in config.headers) {
xhr.setRequestHeader(key,config.headers[key])
}
// 2.4 设置超时
if(config.timeout) {
xhr.timeout = config.timeout
}
// 2.5 处理状态变化
xhr.onreadystatechange = () => {
// 4的状态是请求完成未成功
if(xhr.readyState !== 4) return
// 请求完成,清除超时定时器
if(timeoutTimer) {
clearTimeout(timeoutTimer)
}
// 请求成功
if(xhr.status >= 200 && xhr.status < 300) {
// 响应数据进行处理
let responseData;
if(xhr.responseType === "json") {
responseData = xhr.response
} else {
try {
responseData = JSON.parse(xhr.responseText)
} catch (error) {
responseData = xhr.responseText
}
}
resolve({
data: responseData,
status: xhr.status,
statusText: xhr.statusText,
xhr: xhr
});
} else {
// 请求失败
reject({
status: xhr.status,
statusText: xhr.statusText,
response: xhr.response,
xhr: xhr,
error: new Error(`Request failed with status ${xhr.status}: ${xhr.statusText}`)
});
}
}
// 3.处理网络错误
xhr.onerror = function() {
// 清除超时定时器
if(timeoutTimer) {
clearTimeout(timeoutTimer)
}
reject({
error: new Error('Network error occurred'),
xhr: xhr
});
}
// 4.超时处理
let timeoutTimer;
if(config.timeout) {
timeoutTimer = setTimeout(() => {
xhr.abort()
reject({
error: new Error('Request timed out'),
xhr: xhr
});
},timeout)
}
// 5.发送请求
try {
// 5.1 对于不同的请求方法,POST/PUT/PATCH,需要设置请求体
if(config.method === "POST" || config.method === "PUT" || config.method === "PATCH") {
if(!config.headers['Content-Type']) {
config.headers['Content-Type'] = "application/json"
xhr.setRequestHeader("Content-Type","application/json")
}
const contentType = config.headers['Content-Type'] || ''
let requestData = config.data
if(contentType.includes("application/json") && config.data) {
requestData = JSON.stringify(config.data)
} else if(contentType.includes("application/x-www-form-urlencoded") && config.data) {
const params = new URLSearchParams()
for(let key in config.data) {
params.append(key,config.data[key])
}
requestData = params.toString()
}
xhr.send(requestData)
} else {
// 5.2 GET请求
xhr.send()
}
} catch (error) {
// 清除超时定时器
if(timeoutTimer) {
clearTimeout(timeoutTimer)
}
reject(error)
}
})
}
ajaxPromise.get = (url,data,options = {}) => {
return ajaxPromise({...options,url,method:"GET",data})
}
ajaxPromise.post = (url,data,options = {}) => {
return ajaxPromise({...options,url,method:"POST",data})
}
module.exports = ajaxPromise;
6.测试验证
单元测试,使用了Jest
框架,下面是测试的几个测试用例,完全通过
6.1 实现 async/await 单元测试
javascript
const asyncGenerator = require('../xxx');
// 辅助函数:模拟异步操作
function delay(ms, value) {
return new Promise(resolve => {
setTimeout(() => resolve(value), ms);
});
}
// 辅助函数:模拟失败的异步操作
function failAfter(ms, errorMessage) {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error(errorMessage)), ms);
});
}
describe('asyncGenerator', () => {
// 测试1:基本功能 - 执行简单的Generator函数
test('should execute a simple generator function', async () => {
function* simpleGenerator() {
yield 1;
yield 2;
return 3;
}
const result = await asyncGenerator(simpleGenerator);
expect(result).toBe(3);
});
// 测试2:处理同步值
test('should handle synchronous values', async () => {
function* syncGenerator() {
const a = yield 10;
const b = yield 20;
return a + b;
}
const result = await asyncGenerator(syncGenerator);
expect(result).toBe(30);
});
// 测试3:处理单个异步操作
test('should handle a single asynchronous operation', async () => {
function* asyncGeneratorFunc() {
const result = yield delay(10, 'test');
return result;
}
const result = await asyncGenerator(asyncGeneratorFunc);
expect(result).toBe('test');
});
// 测试4:处理多个串行异步操作
test('should handle multiple sequential async operations', async () => {
function* serialAsyncGenerator() {
const result1 = yield delay(10, 'first');
const result2 = yield delay(10, 'second');
const result3 = yield delay(10, 'third');
return [result1, result2, result3];
}
const result = await asyncGenerator(serialAsyncGenerator);
expect(result).toEqual(['first', 'second', 'third']);
});
// 测试5:处理异步操作的返回值传递
test('should pass values between async operations', async () => {
function* valuePassingGenerator() {
const num1 = yield delay(10, 10);
const num2 = yield delay(10, 20);
return num1 + num2;
}
const result = await asyncGenerator(valuePassingGenerator);
expect(result).toBe(30);
});
// 测试6:捕获异步操作的错误(外部捕获)
test('should catch async errors in external catch', async () => {
function* errorGenerator() {
yield failAfter(10, 'Async error');
}
await expect(asyncGenerator(errorGenerator)).rejects.toThrow('Async error');
});
// 测试7:在Generator内部捕获错误
test('should allow catching errors inside generator', async () => {
function* errorHandlingGenerator() {
try {
yield failAfter(10, 'Expected error');
return 'This should not be reached';
} catch (error) {
return `Caught error: ${error.message}`;
}
}
const result = await asyncGenerator(errorHandlingGenerator);
expect(result).toBe('Caught error: Expected error');
});
// 测试8:处理同步错误
test('should handle synchronous errors', async () => {
function* syncErrorGenerator() {
yield 1;
throw new Error('Sync error');
yield 2; // 不会执行
}
await expect(asyncGenerator(syncErrorGenerator)).rejects.toThrow('Sync error');
});
// 测试9:错误处理后继续执行
test('should continue execution after error handling', async () => {
function* continueAfterErrorGenerator() {
let result;
try {
yield failAfter(10, 'Temporary error');
} catch (error) {
result = error.message;
}
const recovery = yield delay(10, 'Recovered');
return `${result}: ${recovery}`;
}
const result = await asyncGenerator(continueAfterErrorGenerator);
expect(result).toBe('Temporary error: Recovered');
});
});
6.2 Promise 实现 ajax 封装 单元测试
ini
const ajax = require('../4.ajaxPromise');
// 模拟XMLHttpRequest
class MockXHR {
// 静态属性用于保存实例引用
static instance;
constructor() {
// 创建实例时保存到静态属性
MockXHR.instance = this;
this.method = null;
this.url = null;
this.async = true;
this.headers = {};
this.responseType = '';
this.status = 0;
this.statusText = 'OK';
this.response = null;
this.responseText = '';
this.readyState = 0;
this.onreadystatechange = null;
this.onerror = null;
this.ontimeout = null;
}
open(method, url, async) {
this.method = method;
this.url = url;
this.async = async;
this.readyState = 1;
}
setRequestHeader(key, value) {
this.headers[key] = value;
}
send(data) {
this.sendData = data;
// 模拟异步完成
setTimeout(() => {
this.readyState = 4;
if (this.onreadystatechange) {
this.onreadystatechange();
}
}, 0);
}
// 辅助方法,用于测试控制响应
setResponse(status, response, responseType = 'text') {
this.status = status;
this.responseType = responseType;
this.response = response;
this.responseText = typeof response === 'string' ? response : JSON.stringify(response);
}
}
// 替换全局XMLHttpRequest
beforeEach(() => {
global.XMLHttpRequest = MockXHR;
MockXHR.instance = null;
});
describe('ajax', () => {
it('应该处理网络错误', async () => {
// 发送请求
const promise = ajax({
url: 'https://api.example.com/network-error',
method: 'GET'
});
// 获取XHR实例并触发错误
const xhr = MockXHR.instance;
// 模拟网络错误
setTimeout(() => {
if (xhr.onerror) {
xhr.onerror();
}
}, 0);
// 验证错误
await expect(promise).rejects.toHaveProperty('error');
await expect(promise).rejects.toMatchObject({
error: expect.any(Error)
});
});
it('应该发送GET请求并成功返回', async () => {
const mockResponse = { data: 'test' };
// 发送请求
const promise = ajax({
url: 'https://api.example.com/test',
method: 'GET'
});
// 获取最后创建的XHR实例并设置响应
const xhr = MockXHR.instance;
xhr.setResponse(200, mockResponse);
// 等待Promise完成
const result = await promise;
// 验证结果
expect(result.data).toEqual(mockResponse);
expect(result.status).toBe(200);
expect(xhr.method).toBe('GET');
expect(xhr.url).toBe('https://api.example.com/test');
});
it('应该发送POST请求并成功返回', async () => {
const postData = { name: 'test' };
const mockResponse = { success: true };
// 发送请求
const promise = ajax({
url: 'https://api.example.com/submit',
method: 'POST',
data: postData
});
// 获取XHR实例并设置响应
const xhr = MockXHR.instance;
xhr.setResponse(201, mockResponse);
// 等待结果
const result = await promise;
// 验证
expect(result.data).toEqual(mockResponse);
expect(result.status).toBe(201);
expect(xhr.method).toBe('POST');
expect(xhr.sendData).toBe(JSON.stringify(postData));
expect(xhr.headers['Content-Type']).toBe('application/json');
});
it('应该处理请求错误', async () => {
const errorMessage = 'Not Found';
// 发送请求
const promise = ajax({
url: 'https://api.example.com/error',
method: 'GET'
});
// 获取XHR实例并设置错误响应
const xhr = MockXHR.instance;
xhr.setResponse(404, errorMessage);
// 验证错误
await expect(promise).rejects.toHaveProperty('status', 404);
await expect(promise).rejects.toHaveProperty('response', errorMessage);
});
it('应该处理GET请求的查询参数', async () => {
const queryParams = { id: 123, name: 'test' };
// 发送请求
const promise = ajax({
url: 'https://api.example.com/data',
method: 'GET',
data: queryParams
});
// 获取XHR实例并设置响应
const xhr = MockXHR.instance;
xhr.setResponse(200, { success: true });
// 等待结果
await promise;
// 验证URL包含查询参数
expect(xhr.url).toBe('https://api.example.com/data?id=123&name=test');
});
it('应该支持便捷的get方法', async () => {
// 使用便捷方法发送请求
const promise = ajax.get('https://api.example.com/get-test', { param: 'value' });
// 获取XHR实例并设置响应
const xhr = MockXHR.instance;
xhr.setResponse(200, { result: 'success' });
// 验证
const result = await promise;
expect(xhr.method).toBe('GET');
expect(result.data).toEqual({ result: 'success' });
});
it('应该支持便捷的post方法', async () => {
// 使用便捷方法发送请求
const promise = ajax.post('https://api.example.com/post-test', { data: 'value' });
// 获取XHR实例并设置响应
const xhr = MockXHR.instance;
xhr.setResponse(200, { result: 'success' });
// 验证
const result = await promise;
expect(xhr.method).toBe('POST');
expect(result.data).toEqual({ result: 'success' });
});
it('应该正确解析JSON响应', async () => {
const jsonResponse = { name: 'test', value: 123 };
// 发送请求
const promise = ajax({
url: 'https://api.example.com/json',
method: 'GET'
});
// 获取XHR实例并设置响应,模拟Content-Type为JSON
const xhr = MockXHR.instance;
xhr.getResponseHeader = jest.fn(() => 'application/json');
xhr.setResponse(200, jsonResponse);
// 等待结果
const result = await promise;
// 验证JSON被正确解析
expect(result.data).toEqual(jsonResponse);
expect(typeof result.data).toBe('object');
});
});
7.总结
我们对三大异步编程方案进行了横向对比,在日常开发中就可以轻松选择在什么场景使用哪种异步解决方案,三个异步编程方案是层层递进式的,从最初的回调函数(callback)->Promise(解决回调地狱)->Generator(异步线性化思路)->async/await(终极简化,实现以同步方式编写异步代码)。大多数场景中,使用 async/await + Promise 是最优组合,使用async/await串行处理异步流程,用 Promise.all()处理并行任务,兼容可读性和效率。