一、规范简介
-
术语:
- Promise 是具有
then
方法的对象或函数,行为符合Promise/A+规范。 - thenable 是定义then方法得对象或函数
- value 是任何合法的Javascript值(包括undefined,ableable或promise)
- 异常 exception throw 语句抛出的值
- reason 是表明拒绝promise的原因
- Promise 是具有
-
要求
-
promise 的状态
- pending:等待状态,可以转换成其他状态。
- fulfilled:成功状态,不能转化为其他状态。转为该状态需要一个value,该value不能改变。
- rejected:失败状态,不能转为其他状态。转为改状态需要一个reason,该reason不能改变。
fulfilled与rejected是稳定状态
-
then 方法
- const promise2 = promise1.then(onFulfilled, onRejected)
- then方法的参数
- 两个函数参数
- onFulfilled在promise完成后被调用,onRejected在Promise被拒绝执行后调用
- 只能被调用一次
- then方法的调用:可以调用多次
- then方法的返回值:promise
- onFulfilled不是函数,promise1的状态是fulfilled :这时 promise2的状态是fulfilled,值同promise1
- onRejected不是函数,promise1的状态是rejected:这时promise2的状态是 rejected,拒绝原因reason同promise1
- onFulfilled 或者onRejected return x 返回一个返回值,进入Promise解析过程
-
Promise解析过程
- 抽象模型 resolve(promise, x)
- 如果promise和x指向相同的对象
- 如果x是一个promise:查看x的状态 pending 则需等待状态改变,fulfilled value 为 promise的值 ,reject reason为promise的reason
- 如果x是一个对象或一个函数: 执行x的then方法
- 如果x不是对象也不是函数:把x作为promise的值,设置成fulfilled状态
-
二、根据规范打造自己的Promise
-
new Promise
-
定义一个 class 来证明 promise
-
new Promise((resolve, reject) => {})
,传入一个参数(函数),该函数为立即执行函数,这里面我们起名为 executor。 -
executor 函数接收两个参数,resolve 与 reject,成功时resolve(value),失败时reject(reason)。
-
promise 原型方法:
- .then()
- .catch()
- .finally()
-
promise 静态方法
- Promise.resolve(value)
- Promise.reject(reason)
- Promise.all([p1, .....,pn])
- Promise.allSettled([p1, .....,pn])
- Promise.race([p1, .....,pn])
根据以上信息,我们大致架构如下
class Promise { // 构造器 constructor(executor) { // resolve() let resolve = value => {} // reject() let reject = reason => {} // 立即执行 executor 方法 executor(resolve, reject); } // then 方法 then() {} // catch 方法 catch() {} // finall 方法 finally() {} } Promise.resolve = () => {} Promise.reject = () => {} Promise.all = () => {} Promise.allSettled = () => {} Promise.race = () => {}
-
-
promise 状态
规范要求:Promise 状态
- Promise 存在三个状态(state):pending,fulfilled,rejected
- pending 为初始状态,可转化为 fulfilled 和 rejected
- 成功后,不可转为其他状态,且需要一个不可变值(value)
- 失败后,不可转为其他状态,且需要一个不可变原因(reason)
new Promise((resolve, reject) => {})
- 成功时 resolve(value),接受参数value,状态改为 fulfilled,此为稳定状态,不可再次改变。
- 失败时 reject(reason),接受参数 reason,状态改为 rejected,此为稳定状态,不可再次改变。
- 执行 executor 时报错,则直接执行 reject(reason)
根据以上信息,我们得到以下代码:
class Promise {
// 构造器
constructor(executor) {
// 状态 默认为 pending 状态
this.state = "pending";
// 成功的值
this.value = undefined;
// 失败的原因
this.reason = undefined;
// resolve(value) 将状态改为 fulfilled 状态
let resolve = value => {
this.value = value;
this.state = "fulfilled";
}
// reject(reason) 将状态改为 rejected 状态
let reject = reason => {
this.reason = reason;
this.state = "rejected";
}
// 如果 executor 报错 立即执行 reject
try{
// 立即执行 executor 方法
executor(resolve, reject);
} catch(error) {
reject(error);
}
}
// then 方法
then() {}
// catch 方法
catch() {}
// finall 方法
finally() {}
}
Promise.resolve = () => {}
Promise.reject = () => {}
Promise.all = () => {}
Promise.allSettled = () => {}
Promise.race = () => {}
-
.then()
方法规范要求:
.then()
由于
.then()
要求很多,我们进行拆解,分为:- 基本要求
- 链式调用。
1. 基本要求:
-
一个 Promise 必须提供 then 方法,访问其当前或最终value或reason
-
then 接受两个参数
promise.then(onFulfilled, onRejected)
- then的两个参数为可选参数。
- 如果参数不是函数,则必须将其忽略。
- 如果 onFulfilled 是函数
- 必须在promise 成功后调用,以promise的值(value)作为第一个参数,不能调用多次。
- 如果 onRejected 是函数
- 必须在promise 失败后调用,以promise的原因(reason)作为第一个参数,不能调用多次。
- onFulfilled 及 onRejected 必须作为函数调用(没有this值)
-
异步调用:onFulfilled 或 onRejected 在执行上下文堆栈(紧包含平台代码)之前不能调用
class Promise { // 构造器 constructor(executor) {...} // then 方法 then(onFulfilled, onRejected) { // onFulfilled 为可选参数 如果不为函数 则忽略onFulfilled 直接返回 value onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value; // onRejected 为可选参数 如果不为函数 则忽略onFulfilled 直接抛出错误 onRejected = isFunction(onRejected) ? onRejected : error => { throw error}; // 如果 Promise 为 成功状态 调用 onFulfilled 方法 传入成功的 value if(this.state === "fulfilled") { // 异步调用 setTimeout(() => { onFulfilled(this.value); }, 0); } // 如果 Promise 为 失败状态 调用 onRejected 方法 传入失败的 reason if (this.state === "rejected") { // 异步调用 setTimeout(() => { onRejected(this.reason); }, 0); } } // catch 方法 catch() {} // finall 方法 finally() {} } Promise.resolve = () => {} Promise.reject = () => {} Promise.all = () => {} Promise.allSettled = () => {} Promise.race = () => {} // 辅助方法 // 判断 obj 是不是 Function function isFunction(obj) { return !!obj && typeof obj === "function"; } // 判断 obj 是不是 Object function isObject(obj) { return !!obj && typeof obj === "object"; } // 判断 obj 是不是 Promise function isPromise(obj) { return !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function"; }
特殊情况:
new Promise((resolve, reject) => { setTimeout(() => { resolve(1); // reject("错误"); }, 200) })
当 resolve 或者reject 在 setTimeout内执行时,由于setTimeout为异步方法,会先把它放到宏任务队列当中等待当前任务执行完成之后在执行。当前任务执行到.then 时 Promise的状态(state)还是pending(等待状态),这时就会出现问题。
解决方案:
规范要求中有一条要求:
-
then 可能在同一 Promise中多次被调用
let p = new Promise(); p.then(); p.then();
- 当promise 成功,则所有相应的 onFulfilled回调必须按照其原始调用的执行顺序执行then
- 当promise失败,则所有相应的onRejected回调必须按照其原始调用的执行顺序进行then
根据这一要求,我们可以在then中增加 state为pending的处理,将onFulfilled 与onRejected方法存到各自的数组里面,一旦setTimeout中的resolve或者reject执行了 ,就调用它们。
这样除了能解决setTimeout异步问题,也能解决统一Promise多次调用then问题。
根据以上信息,得到以下代码:
class Promise { constructor(executor) { this.state = "pending"; this.value = undefined; this.reason = undefined; // 成功存放方法的数组 this.onResolvedCallbacks = []; // 失败存放方法的数组 this.onRejectedCallbacks = []; let resolve = value => { this.value = value; this.state = "fulfilled"; // 一旦 resolve 执行, 则按顺序 调用 成功数组 中的函数 this.onResolvedCallbacks.forEach(fn => { fn(); }); } let reject = reason => { this.reason = reason; this.state = "rejected"; // 一旦 reject 执行, 则按顺序 调用 失败数组 中的函数 this.onRejectedCallbacks.forEach(fn => { fn(); }); } try{ executor(resolve, reject); } catch(error) { reject(error); } } // then 方法 then(onFulfilled, onRejected) { onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value; onRejected = isFunction(onRejected) ? onRejected : error => { throw error}; if(this.state === "fulfilled") { setTimeout(() => { onFulfilled(this.value); }, 0); } if (this.state === "rejected") { setTimeout(() => { onRejected(this.reason); }, 0); } // 如果 Promise 为 等待状态 // 由于不知道 是成功还是失败,则需要两个数组, // 将成功或失败各自对应的方法 push 到各自的数组当中 if (this.state === "pending") { // onFulfilled 传入到成功数组 this.onResolvedCallbacks.push(() => { // 异步调用 setTimeout(() => { onFulfilled(this.value); }, 0); }); // onRejected 传入到成功的数组 this.onRejectedCallbacks.push(() => { // 异步调用 setTimeout(() => { onRejected(this.reason); }, 0); }) } } catch() {} finally() {} } Promise.resolve = () => {} Promise.reject = () => {} Promise.all = () => {} Promise.allSettled = () => {} Promise.race = () => {} function isFunction(obj) { return !!obj && typeof obj === "function"; } function isObject(obj) { return !!obj && typeof obj === "object"; } function isPromise(obj) { return !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function"; }
2. 链式调用
日常使用 Promise 时,常用到 new promise().then().then(),这就是链式调用。通过链式调用,我们就可以解决回调地狱问题。
规范中规定:
-
then 必须返回一个promise
promise2 = promise1.then(onFulfilled, onRejected);
- 如果 onFulfilled 或 onRejected 执行时报错,则直接返回失败
- onFulfilled 不是函数,promise1的状态是fulfilled :这时 promise2的状态是fulfilled,值同promise1
- onRejected 不是函数,promise1 的状态是 rejected:这时promise2的状态是 rejected,拒绝原因 reason 同promise1
- 如果一个 onFulfilled 或 onRejected 返回一个值 x(自己return x),则需要对x进行处理,处理的函数叫做:resolvePromise(promise2, x)
-
规范中的resolvePromise(promise2, x) 规定
- 如果promise和x指向相同的对象,会导致循环调用,直接返回失败
- 如果x是一个promise:查看x的状态 pending 则需等待状态改变,fulfilled value 为 promise的值 ,reject reason为promise的reason
- 如果x是一个对象或一个函数: 执行x的then方法
- 如果 没有x.then 则直接返回失败
- 如果 有x.then 且为函数,则执行 then.call(),参数:x 作为this,成功时的回调,失败时的回调。
- 如果成功的回调还是promise,值为y,再次调用 resolvePromise(promise2, y, resolve, reject)
- 如果失败,则执行失败回到,原因为r,直接返回失败
- 成功和失败只能调用一个,所以设定一个isCalled来防止多次调用
- 如果 有x.then但不是函数,则将x作为promise的值
- 如果x不是对象也不是函数:把x作为promise的值,设置成fulfilled状态
class Promise { // 构造器 constructor(executor) {...} // then 方法 then(onFulfilled, onRejected) { // onFulfilled 为可选参数 如果不为函数 则忽略onFulfilled 直接返回 value onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value; // onRejected 为可选参数 如果不为函数 则忽略onFulfilled 直接抛出错误 onRejected = isFunction(onRejected) ? onRejected : error => { throw error}; // 如果 Promise 为 成功状态 调用 onFulfilled 方法 传入成功的 value if(this.state === "fulfilled") { // 异步调用 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); } // 如果 Promise 为 失败状态 调用 onRejected 方法 传入失败的 reason if (this.state === "rejected") { // 异步调用 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); } // 如果 Promise 为 等待状态 // 由于不知道 是成功还是失败,则需要两个数组, // 将成功或失败各自对应的方法 push 到各自的数组当中 if (this.state === "pending") { // onFulfilled 传入到成功数组 this.onResolvedCallbacks.push(() => { // 异步调用 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); }); // onRejected 传入到成功的数组 this.onRejectedCallbacks.push(() => { // 异步调用 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); }); } } // catch 方法 catch() {} // finall 方法 finally() {} } Promise.resolve = () => {} Promise.reject = () => {} Promise.all = () => {} Promise.allSettled = () => {} Promise.race = () => {} function resolvePromise(promise2, x, resolve, reject) { // 如果 x 和 promise2 指向相同 if (x === promise2) { reject(new TypeError("Chaining cycle detected for promise")); } // 如果x为promise 则采用其状态 if (isPromise(x)) { if (x.state === "pending") { return x.then(()=> { resolve(x.value); }, () => { reject(x.reason); }) } if (x.state === "fulfilled") { return resolve(x.value); } if (x.state === "rejected") { return reject(x.reason); } } else if (isObject(x) || isFunction(x)) { // 如果x是对象或者函数 let then; // 取 x.then try { then = x.then; } catch (error) { // 取x.then 如果报错则直接失败 reject(error); } // 如果 then 是 函数 if (isFunction(then)) { // 只可以调用一个 let isCalled = false; try { then.call(x, y => { if (isCalled) { return; } isCalled = true; resolvePromise(promise2, y, resolve, reject); }, r => { if (isCalled) { return; } isCalled = true; reject(r); }) } catch (error) { // 执行then报错则直接返回失败 if (isCalled) { return; } isCalled = true; reject(error); } } else { // 如果不是方法 则把x作为promsie的值 resolve(x); } } else { // x 既不是方法也不是函数 则把x作为promsie的值 resolve(x); } } // 辅助方法 // 判断 obj 是不是 Function function isFunction(obj) { return !!obj && typeof obj === "function"; } // 判断 obj 是不是 Object function isObject(obj) { return !!obj && typeof obj === "object"; } // 判断 obj 是不是 Promise function isPromise(obj) { return !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function"; }
-
catch 实现
报错就走这个方法
// catch 方法 catch(fn) { return this.then(null, fn); }
-
finall 实现
不管成功还是失败,最后都要执行该方法,与 then不同
参数:onFinally :Promise结束后调用的function
返回值: 返回了一个设置了finally回调函数的Promise对象
描述:如果你想在 promise 执行完毕后无论其结果怎样都做一些处理或清理时,
finally()
方法可能是有用的。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
。
注意: 在
finally
回调中throw
(或返回被拒绝的promise)将以throw()
指定的原因拒绝新的promise.// finall 方法 finally(fn) { return new Promise((resolve, reject) => { try { fn(); } catch (error) { reject(error); } if (this.state === "fulfilled") { resolve(this.value); } if (this.state === "rejected") { reject(this.reason); } }) }
-
Promise.resolve 实现
Promise.resolve = (value) => { return new Promise((resolve, reject) => { resolve(value); }); }
-
Promise.reject 实现
Promise.reject = (reason) => { return new Promise((resolve, reject) => { resolve(reason); }); }
-
Promise.all 实现
all 方法
-
Promise.all([p1, p2, p3])用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise
-
它接收一个数组作为参数
-
数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态改变
-
当所有的子Promise都完成,该Promise完成,返回值是全部值得数组
-
有任何一个失败,该Promise失败,返回值是第一个失败的子Promise结果
Promise.all = (promiseArr) => { let result = []; let i = 0; function handleData(index, value, resolve) { result[index] = value; i++; if (i == promiseArr.length) { resolve(result); } } return new Promise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { if (isPromise(promiseArr[i])) { promiseArr[i].then(value => { handleData(i, value, resolve); }, reject) } else { handleData(i, promiseArr[i], resolve); } } }) }
-
-
Promise.allSettled 实现
allSettled 方法
-
Promise.allSettled([p1, p2, p3])用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise
-
它接收一个数组作为参数
-
数组里是Promise对象
-
当所有的子Promise都完成,该Promise完成,返回值是一个对象 包含每个子promise执行后得状态及对应得值或原因
-
只要所有得子promise都执行完成,结果Promise为fulfilled状态
Promise.allSettled = (promiseArr) => { let result = []; let i = 0; function handleData(index, data, resolve) { result[index] = data; i++; if (i == promiseArr.length) { resolve(result); } } return new promise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { promiseArr[i].then(value => { handleData(i, {state: "fulfilled", value: value}, resolve); }, error => { handleData(i, {state: "rejected", reason: error}, resolve); }); } }); }
-
-
Promise.race 实现
race 方法 有一个完成就算完成,返回的promise状态同第一个完成的
Promise.race = (promiseArr) => { return new promise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { promiseArr[i].then(resolve, reject); } }); }
-
完整代码
class Promise { // 构造器 constructor(executor) { // 状态 默认为 pending 状态 this.state = "pending"; // 成功的值 this.value = undefined; // 失败的原因 this.reason = undefined; // 成功存放方法的数组 this.onResolvedCallbacks = []; // 失败存放方法的数组 this.onRejectedCallbacks = []; // resolve(value) 将状态改为 fulfilled 状态 let resolve = value => { this.value = value; this.state = "fulfilled"; // 一旦 resolve 执行, 则按顺序 调用 成功数组 中的函数 this.onResolvedCallbacks.forEach(fn => { fn(); }); } // reject(reason) 将状态改为 rejected 状态 let reject = reason => { this.reason = reason; this.state = "rejected"; // 一旦 reject 执行, 则按顺序 调用 失败数组 中的函数 this.onRejectedCallbacks.forEach(fn => { fn(); }); } // 如果 executor 报错 立即执行 reject try{ // 立即执行 executor 方法 executor(resolve, reject); } catch(error) { reject(error); } } // then 方法 then(onFulfilled, onRejected) { // onFulfilled 为可选参数 如果不为函数 则忽略onFulfilled 直接返回 value onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value; // onRejected 为可选参数 如果不为函数 则忽略onFulfilled 直接抛出错误 onRejected = isFunction(onRejected) ? onRejected : error => { throw error}; // 如果 Promise 为 成功状态 调用 onFulfilled 方法 传入成功的 value if(this.state === "fulfilled") { // 异步调用 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); } // 如果 Promise 为 失败状态 调用 onRejected 方法 传入失败的 reason if (this.state === "rejected") { // 异步调用 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); } // 如果 Promise 为 等待状态 // 由于不知道 是成功还是失败,则需要两个数组, // 将成功或失败各自对应的方法 push 到各自的数组当中 if (this.state === "pending") { // onFulfilled 传入到成功数组 this.onResolvedCallbacks.push(() => { // 异步调用 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); }); // onRejected 传入到成功的数组 this.onRejectedCallbacks.push(() => { // 异步调用 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, 0); }); } } // catch 方法 catch(fn) { return this.then(null, fn); } // finall 方法 finally(fn) { return new Promise((resolve, reject) => { try { fn(); } catch (error) { reject(error); } if (this.state === "fulfilled") { resolve(this.value); } if (this.state === "rejected") { reject(this.reason); } }) } } Promise.resolve = (value) => { return new Promise((resolve, reject) => { resolve(value); }); } Promise.reject = (reason) => { return new Promise((resolve, reject) => { resolve(reason); }); } Promise.all = (promiseArr) => { let result = []; let i = 0; function handleData(index, value, resolve) { result[index] = value; i++; if (i == promiseArr.length) { resolve(result); } } return new Promise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { if (isPromise(promiseArr[i])) { promiseArr[i].then(value => { handleData(i, value, resolve); }, reject) } else { handleData(i, promiseArr[i], resolve); } } }) } Promise.allSettled = (promiseArr) => { let result = []; let i = 0; function handleData(index, data, resolve) { result[index] = data; i++; if (i == promiseArr.length) { resolve(result); } } return new promise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { promiseArr[i].then(value => { handleData(i, {state: "fulfilled", value: value}, resolve); }, error => { handleData(i, {state: "rejected", reason: error}, resolve); }); } }); } Promise.race = (promiseArr) => { return new promise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { promiseArr[i].then(resolve, reject); } }); } function resolvePromise(promise2, x, resolve, reject) { // 如果 x 和 promise2 指向相同 if (x === promise2) { reject(new TypeError("Chaining cycle detected for promise")); } // 如果x为promise 则采用其状态 if (isPromise(x)) { if (x.state === "pending") { return x.then(()=> { resolve(x.value); }, () => { reject(x.reason); }) } if (x.state === "fulfilled") { return resolve(x.value); } if (x.state === "rejected") { return reject(x.reason); } } else if (isObject(x) || isFunction(x)) { // 如果x是对象或者函数 let then; // 取 x.then try { then = x.then; } catch (error) { // 取x.then 如果报错则直接失败 reject(error); } // 如果 then 是 函数 if (isFunction(then)) { // 只可以调用一个 let isCalled = false; try { then.call(x, y => { if (isCalled) { return; } isCalled = true; resolvePromise(promise2, y, resolve, reject); }, r => { if (isCalled) { return; } isCalled = true; reject(r); }) } catch (error) { // 执行then报错则直接返回失败 if (isCalled) { return; } isCalled = true; reject(error); } } else { // 如果不是方法 则把x作为promsie的值 resolve(x); } } else { // x 既不是方法也不是函数 则把x作为promsie的值 resolve(x); } } // 辅助方法 // 判断 obj 是不是 Function function isFunction(obj) { return !!obj && typeof obj === "function"; } // 判断 obj 是不是 Object function isObject(obj) { return !!obj && typeof obj === "object"; } // 判断 obj 是不是 Promise function isPromise(obj) { return !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function"; }
-
验证promise是否正确
-
先在后面加上下述代码
-
npm 有一个promises-aplus-tests插件 npm i promises-aplus-tests -g 可以全局安装 mac用户最前面加上sudo
-
命令行 promises-aplus-tests [js文件名] 即可验证
// 目前是通过他测试 他会测试一个对象 // 语法糖 Promise.defer = Promise.deferred = function () { let dfd = {} dfd.promise = new Promise((resolve,reject)=>{ dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } module.exports = Promise; //npm install promises-aplus-tests 用来测试自己的promise 符不符合promisesA+规范
-
网友评论