前言
一直都想写关于Promise的东西,Promise解决的问题特别多,而普通前端就把这东西结合ajax来做一个await request()
,如果仅仅作为这样一种东西使用那就太可惜了。
Promise 就是一个任务包的状态控制对象,new Promise后,内部会传入一个函数,这个函数里面有一个执行过程,这个过程往往是异步的,但不管里面的逻辑如何,最终只要调用Promise给的resolve(),就代表这个任务包被处理完毕了,所以这个时间是不确定的,之后就能做Promise.then的事情了。
这在Node.js开发的协程服务简直完美,可以这样理解,原来基于线程的Java会为每个请求建立一个线程,线程的调度是CPU级别的,里面的变量也不是共享的,处理起来比较麻烦,而且每个线程都是独立的,只要线程处于等待状态,里面的拷贝的变量都会一直存在,对内存,对cpu都有消耗。
之后node.js协程调度来了,所有的请求都只是一个协程,每个请求被作为一个队列堆起来,所有的系统上下文都在一个线程里面,无需拷贝,调度过程也是yield语句直接切换,cpu和内存消耗都大大降低,也没有线程的概念,这对于高并发长等待的请求来说,简直完美,就可以理解为java能开300个线程,而换成了Node.js可以开上万个连接支持websocket,因为所有建立的websocket都在一个进程里,上下文并没有单开线程,也没有线程间的调度,最方便的是,引入了Pomise的话,用户根本不用区分那个协程的请求,Promise本身会将这些变量保存起来
比如说,请求1发过来的req,如果作为闭包函数塞入Promise((r,rj) => {req,res}),那么等这个请求完全处理完成后,再调用res.json(xxx) 时,这里的res自然就会调用本次请求的协程上下文,返回给请求的结果,而无需你再来写什么调度逻辑,去找到这个连接的fd,然后再实例化这个fd,然后再发送出去了,方便地不能再方便了。
如果你看看其他语言,例如Go,python,或者hyperf(php协程框架),就会知道Promise的简便性是无法比拟的,只要你用了一次,就会被这种简单紧紧吸引。
它是队列的任务包
前端同学应该是没听说过队列,但是大前端同学,例如搞安卓的或者IOS的肯定听过,在安卓开发种,所有的UI操作都需要给主进程处理,这里的new Runnable() 就很像Promise
安卓里面的线程子类
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
handler = new Handler(Looper.getMainLooper());
// 启动一个子线程
new Thread(new Runnable() {
@Override
public void run() {
// 模拟一些后台工作
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新UI,必须在主线程中进行
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("更新后的文本");
}
});
}
}).start();
}
}
Python 协程中的Future
import asyncio
def async_task(future, success=True):
async def task():
await asyncio.sleep(2)
if success:
future.set_result("Task completed successfully")
else:
future.set_exception(ValueError("Something went wrong"))
return task()
def success_callback(result):
print(f"Success: {result}")
def error_callback(error):
print(f"Error: {error}")
async def main():
future = asyncio.Future()
# 启动异步任务
asyncio.create_task(async_task(future))
# 注册成功和错误回调
future.add_done_callback(lambda f: success_callback(f.result()) if not f.exception() else error_callback(f.exception()))
# 等待 Future 完成
await future
# 运行事件循环
asyncio.run(main())
Promise 的异步转同步操作
function updateDomElement(element, newValue) {
return new Promise((resolve, reject) => {
// 假设这里是一个异步的DOM更新操作
element.textContent = newValue;
// 模拟DOM更新完毕,触发一个自定义事件
const event = new Event('domUpdated');
setTimeout(() => {
element.dispatchEvent(event);
}, 1000); // 1秒后触发更新完成事件
// 监听自定义事件,表示DOM更新完毕
element.addEventListener('domUpdated', function onDomUpdated() {
element.removeEventListener('domUpdated', onDomUpdated);
resolve(true); // 触发Promise的resolve
});
// 如果有可能的错误情况,也可以调用reject(error)
});
}
// 使用示例
async function performOperation() {
const element = document.getElementById('myElement');
// 更新DOM元素,并等待更新完成
await updateDomElement(element, "New Content");
// DOM更新完成后继续操作
console.log("DOM updated successfully. Now performing next operations...");
// 继续后续操作
}
performOperation();
创建一个Promise,直到有人调用了这个Promise.resolve()函数
只要你理解了上面任务包的概念之后,并且你知道只要有人调用了Promise.resolve()这个函数后,这个任务包就处理成功了,那么只要你创建了它,就相当于塞到了事件循环队列里去了。
await就是一个生产者,同时还把它下面的代码都自动封装成了回调函数,之后的就没有自己的故事了。
async 相当于一个消费者,负责等待任务逻辑调用这个Promise.resolve()函数,一旦有逻辑调用了Promise.resolve函数,就意味着这个Promise任务被处理完毕了,就可以开始它的后续回调了。
Promise都有哪些用途?
-
你试试用promise将websocket封装为一个await wsrequest(),这样能让你像调用http一样调用websocket,非常方便,怎么搞?
-
插件与网页的异步通信,上面已有演示,通过事件的方式,来回倒腾,实现同步请求
-
网页UI的动画或者状态变化的后续操作,demo省略
-
Electron中各种复杂调度下的异步转同步操作
总之把Promise运用在异步转同步的构建上,才能发挥出Promise存在的意义,可以理解为一切异步操作都可以用Promise来封装,实现完美的调度逻辑。